/**
 * 函数模型
 *
 * Created by snowingsea on 2020/03/04.
 */
const mongoose = require('mongoose');
const _ = require('underscore');
const fs = require('fs-extra');
const path = require('path');
const randomstring = require('randomstring');
const { v4: uuid } = require('uuid');
const Filter = require('./mongo/filter');
const Func = require('./mongo/function');
const FunctionCFG = require('./mongo/function-cfg');
const FunctionVariable = require('./mongo/function-variable');
const FunctionOriginalVariable = require('./mongo/function-original-variable');
const TestCase = require('./mongo/test-case');
const TestCaseGroup = require('./mongo/test-case-groups');
const TestCaseCoveredField = require('./mongo/test-case-covered-field');
const ProjectVersion = require('./mongo/project-version');
const tests = require('./tests');
const common = require('./common');
const cacheUtil = require('../cacheUtil');
const publisher = require('./redis/publisher');
const utility = require('./utility');
const fileElement = require('./file-elements');
const codeDefects = require('./code-defects');
const UserDefinedFields = require('./mongo/user-defined-fields');
const PhoenixError = require('./phoenix-error');
const invokeRelations = require('./invoke-relations');
const InvokeRelation = require('./mongo/invoke-relation');
const functionRelations = require('./function-relations');
const File = require('./mongo/file');
const projectVersions = require('./project-versions');
const FileGlobalVariable = require('./mongo/file-global-variable');
const handleIntegrationVariables = require('./handle-integration-variables');
const fileModels = require('./files');
const Excel = require('./export/report/adapter/excel');
const { eventHub, eventKind } = require('./event-hub');
const TestInvoke = require('./mongo/test-invoke');
const FileComment = require('./mongo/file-comment');
const FuncBorderVariable = require('./mongo/function-border-variable');
const BlackBoxCovered = require('./mongo/black-box-covered');
const ManualCovered = require('./mongo/manual-covered');
const BreakPoint = require('./mongo/break-point');
const logger = require('./logger');
const testCaseGroups = require('./test-case-groups');

// 绝对地址目标类型
const fixedAddrBasesAssignmentKind = {
  baseOffset: 1, // 偏移量
  baseExprs: 2, // 表达式
};
// 集成解决方案更新文件变量类型
const integrationSolutionVariableType = {
  global: 1, // 全局变量
  fixedAddrBases: 2, // 绝对地址目标
};
/**
 * 集成函数解决方案分析结果
 * @type {Object}
 */
const integrationAnalysisResult = {
  undefined: 1, // 未定义
  duplicateDefined: 2, // 重复定义
  defined: 3, // 定义
};
/**
 * CFG节点类型
 * @type {Object}
 */
const cfgNodeKind = {
  entry: 1,
  exit: 2,
  basic: 3,
  conditionIf: 4,
  conditionFor: 5,
  conditionWhile: 6,
  conditionDoWhile: 7,
  conditionSwitch: 8,
  conditionRange: 9,
  returnStat: 10,
};

const resultKind = {
  undefined: 'undefined',
  failed: 'failed',
  passed: 'passed',
};
// 变量种类
const variableKind = {
  global: 1, // 全局变量
  param: 2, // 形参
  mallocVariable: 3, // 指针目标
  return: 4, // 返回值
  stub: 10, // 桩函数
  stubReturn: 11, // 桩函数返回值
  stubParam: 12, // 桩函数参数
  stubVariadicParam: 61, // 桩函数可变参数
  stubPointerTarget: 13, // 桩函数指针目标
  stubMemberVariable: 14, // 桩函数成员变量
  constructorParam: 15, // 构造函数参数
  fixedAddressExpression: 20, // 绝对地址
  fixedAddressBase: 21, // 绝对地址目标
  fixedAddressOffset: 22, // 绝对地址偏移量
  statics: 23, // 静态变量
  exception: 30, // 异常
  object: 40, // 成员函数所属类
  integratedFunc: 24, // 集成函数
  integratedFuncParam: 25, // 集成函数参数
  integratedFuncPointerTarget: 26, // 集成函数指针目标

};
// 变量范围
const variableScopeKind = {
  input: 1, // 输入
  output: 2, // 输出
};

const integratedFuncKey = 'integratedFuncs';

const compatibleKey = '2.6.9';

/**
 * 获取CFG的json信息
 * @param {FunctionCFG} functionCFG 函数CFG信息
 * @return {Object}
 */
function getCFGJson(functionCFG) {
  const json = { functionId: functionCFG.functionId, nodes: [], edges: [] };
  if (functionCFG.decisions) {
    Object.keys(functionCFG.decisions).forEach((decisionId) => {
      const decision = functionCFG.decisions[decisionId];
      const nodeId = decision.nodeId.toString();
      const conditionsCover = {};
      if (functionCFG.decisions[decisionId].conditionCoveredPairs) {
        const { conditionCoveredPairs } = functionCFG.decisions[decisionId];
        Object.keys(conditionCoveredPairs).forEach((conditionId) => {
          let covered = false;
          conditionsCover[conditionId] = {
            coveredPairs: conditionCoveredPairs[conditionId],
          };
          conditionCoveredPairs[conditionId].forEach(([i, j]) => {
            if (covered) {
              return;
            }
            if (!utility.isEmpty(decision.cases[i].testIds)
              && !utility.isEmpty(decision.cases[j].testIds)
            ) {
              covered = true;
            }
          });
          conditionsCover[conditionId].covered = covered;
        });
      }
      if (!functionCFG.nodes[nodeId].decisions) {
        functionCFG.nodes[nodeId].decisions = [];
      }
      functionCFG.nodes[nodeId].decisions.push({
        decisionId: parseInt(decisionId, 10),
        expr: decision.expr,
        conditions: decision.conditions,
        cases: Object.values(decision.cases),
        conditionsCover,
        begin: decision.begin,
        end: decision.end,
      });
    });
  }
  if (functionCFG.nodes) {
    const nodes = Object.values(functionCFG.nodes);
    json.nodes = nodes.map((node) => {
      node.label = (nodes.length - node.nodeId).toString();
      return node;
    });
  }
  if (functionCFG.edges) {
    json.edges = Object.values(functionCFG.edges);
  }
  return json;
}

/**
 * 获取指针目标变量json信息
 * @param {String} functionId 函数id
 * @param {String} variableName 变量名
 * @param {Object} variable 变量信息
 * @return {Object}
 */
function getMallocVariableJson(functionId, variableName, variable) {
  const result = {
    functionId,
    variableName,
    typeName: variable['@type'],
    elementTypeName: variable['@elementType'],
    length: variable['@length'],
    constructor: '',
  };
  if (!variable['@init']) {
    return result;
  }
  if (variable['@elementType']) {
    result.constructor = variable['@init']['#']['@init']['@ctor']['@value'];
  } else {
    result.constructor = variable['@init']['@ctor']['@value'];
  }
  return result;
}

/**
 * 获取桩函数json信息
 * @param {String} functionId 函数id
 * @param {String} stubName 桩函数名
 * @param {Object} stub 桩数据
 * @return {Object}
 */
function getStubJson(functionId, stubName, stub) {
  const result = {
    functionId,
    stubName,
    defaultKind: stub.defaultKind || tests.stubKind.value,
    times: stub.times || 0,
    defaultTimes: stub.defaultTimes || 0,
    return: stub['%'],
    params: stub.params,
  };
  if (stub.modifiable) {
    result.modifiable = stub.modifiable;
  }
  if (stub.pointerTargets) {
    result.pointerTargets = stub.pointerTargets;
  }
  return result;
}

/**
 * 获取桩函数指针目标变量json信息
 * @param {String} functionId 函数id
 * @param {String} stubName 桩函数名
 * @param {String} pointerTargetName 指针目标名
 * @param {Object} pointerTarget 指针目标数据
 * @return {Object}
 */
function getStubPointerTargetJson(functionId, stubName, pointerTargetName, pointerTarget) {
  return {
    functionId,
    stubName,
    pointerTargetName,
    typeName: pointerTarget['@type'],
    elementTypeName: pointerTarget['@elementType'],
    length: pointerTarget['@length'],
  };
}

/**
 * 获取绝对地址json信息
 * @param {String} functionId 函数id
 * @param {String} fixedAddressName 绝对地址名
 * @param {Object} address 绝对地址
 * @return {{functionId: *, fixedAddressName: *}}
 */
function getFixedAddressJson(functionId, fixedAddressName, address) {
  const { pointerTargets } = address;
  return {
    functionId,
    fixedAddressName,
    pointerTargets,
    length: address['@length'],
    hex: address['@hex'],
    assignmentKind: address.assignmentKind || fixedAddrBasesAssignmentKind.baseOffset,
  };
}

/**
 * 取消设置全局变量检查项
 * @param {String} functionId 函数id
 * @param {String} variableName 变量名
 * @return {Promise}
 */
function removeOutputFixedAddress(functionId, variableName) {
  const doc = { $unset: {} };
  doc.$unset[`output.fixedAddrs.${variableName}`] = 1;
  const promises = [
    FunctionVariable.updateOne({ functionId }, doc),
    tests.removeOutputFixedAddress(functionId, variableName),
  ];
  return Promise.all(promises);
}

async function getRawFunction(functionId) {
  return Func.findOne({ _id: mongoose.Types.ObjectId(functionId) });
}

/**
 * 获取函数信息
 * @param {String} functionId 函数id
 * @return {Promise}
 */
async function getFunction(functionId) {
  const [exists] = await Func.aggregate(
    [
      { $match: { _id: mongoose.Types.ObjectId(functionId) } },
      {
        $lookup: {
          from: 'projectversions',
          localField: 'versionId',
          foreignField: '_id',
          as: 'versionType',
        },
      },
      { $unwind: '$versionType' },
      {
        $addFields: { versionType: '$versionType.versionType' },
      },
      {
        $lookup: {
          from: 'files',
          localField: 'fileId',
          foreignField: '_id',
          as: 'filepath',
        },
      },
      { $unwind: '$filepath' },
      {
        $addFields: { filepath: '$filepath.path' },
      },
    ]
  );
  if (!exists) {
    return null;
  }
  const cond = { scope: 'function', $or: [{ domain: exists.versionType }] };
  // 兼容老的数据
  if (exists.versionType === projectVersions.versionType.unitTest) {
    cond.$or.push({ domain: { $exists: false } });
  }
  const udfs = await UserDefinedFields.find(cond, {}, { sort: { index: 1 } });
  const userDefinedFields = exists.userDefinedFields || [];
  exists.userDefinedFields = udfs.map(({ _id, name: displayName }) => {
    const assigned = userDefinedFields.find((u) => _id.equals(u.key));
    return {
      key: _id,
      name: displayName,
      value: assigned ? assigned.value : null,
    };
  });
  return exists;
}

/**
 * 通过函数名获取函数信息
 * @param {String} fileId 文件id
 * @param {String} functionName 函数名
 * @return {Promise}
 */
function getFunctionByName(fileId, functionName) {
  return Func.findOne({
    fileId: mongoose.Types.ObjectId(fileId),
    $or: [
      { functionName },
      { mangledId: functionName },
    ],
  });
}

/**
 * 获取项目版本键值
 * @param {String} fileId 文件id
 * @param {String} functionName 函数名称
 * @return {String}
 */
function getFunctionKey(fileId, functionName) {
  return `${fileId}:${functionName}`;
}

/**
 * 通过唯一健获取函数信息
 * @param {String} fileId 文件id
 * @param {String} key 唯一健
 * @return {Promise}
 */
function getFunctionByKey(fileId, key) {
  key = getFunctionKey(fileId, key);
  return Func.findOne({ fileId: mongoose.Types.ObjectId(fileId), key });
}

/**
 * 获取函数列表
 * @param {String} versionId 项目版本id
 * @param {Array} functionIds 函数id列表
 * @param {Array} exceptFunctionIds 不计入的函数id列表
 * @param {Boolean} collectCoverage 是否收集覆盖率
 * @param {Object} sort 排序字段
 * @param {Array} fileIds 文件id列表
 * @return {Promise}
 */
function getFunctions(
  versionId,
  {
    functionIds = undefined,
    exceptFunctionIds = undefined,
    collectCoverage = undefined,
    kinds = undefined,
    sort = { index: 1 },
    projection = undefined,
  } = {},
  fileIds = undefined // eslint-disable-line comma-dangle
) {
  return new Promise((resolve, reject) => {
    if (fileIds) {
      if (!utility.isArrayType(fileIds)) {
        fileIds = [fileIds];
      }
      fileIds = fileIds.map((id) => mongoose.Types.ObjectId(id));
    }

    const idOptions = {};
    if (functionIds) {
      if (!utility.isArrayType(functionIds)) {
        functionIds = [functionIds];
      }
      idOptions.$in = functionIds.map((projectId) => mongoose.Types.ObjectId(projectId));
    } else {
      if (!exceptFunctionIds) {
        exceptFunctionIds = [];
      }
      if (!utility.isArrayType(exceptFunctionIds)) {
        exceptFunctionIds = [exceptFunctionIds];
      }
      idOptions.$nin = exceptFunctionIds.map((projectId) => mongoose.Types.ObjectId(projectId));
    }

    const options = { versionId: mongoose.Types.ObjectId(versionId) };
    if (kinds && kinds.length) {
      options.kind = { $in: kinds };
    }
    if (collectCoverage) {
      options.collectCoverage = collectCoverage;
    }
    if (fileIds) {
      if (functionIds) {
        options.$or = [{ _id: idOptions }, { fileId: { $in: fileIds } }];
      } else {
        options._id = idOptions; // eslint-disable-line no-underscore-dangle
        options.fileId = { $in: fileIds };
      }
    } else {
      options._id = idOptions; // eslint-disable-line no-underscore-dangle
    }
    Func.find(
      options,
      !utility.isEmpty(projection) ? projection : {},
      !utility.isEmpty(sort) ? { $sort: sort } : {}
    )
      .then((functions) => {
        resolve(functions);
      })
      .catch(reject);
  });
}

/**
 * 筛选函数列表
 * @param {String} fileId 文件id
 * @param {Object} pagination 排序信息
 * @param {Number|Array} status 函数状态
 * @return {Promise}
 */
function filter(fileId, pagination = {}, { status = undefined } = {}) {
  fileId = mongoose.Types.ObjectId(fileId);
  const options = { fileId };
  if (status) {
    if (!utility.isArrayType(status)) {
      status = [status];
    }
    options.status = { $in: status };
  }
  const { sortBy } = pagination;
  if (!sortBy || (sortBy === 'functionId')) {
    pagination.sortBy = '_id';
  }
  pagination.perPage = 0;
  return Filter.filter(Func, pagination, options);
}

/**
 * 获取某个文件的函数变量列表信息
 * @param {String} fileId 文件id
 * @return {Promise}
 */
function getVariablesOfFile(fileId, versionId) {
  const filter = {};
  if (versionId) {
    filter.versionId = mongoose.Types.ObjectId(versionId);
  }
  filter.fileId = mongoose.Types.ObjectId(fileId);
  return FunctionVariable.find(filter);
}

/**
 * 获取指定函数列表的函数变量列表信息
 * @param {Array} functionIds 函数id列表
 * @return {Promise}
 */
function getVariablesOfFunctions(functionIds) {
  if (!functionIds) {
    functionIds = [];
  }
  if (!utility.isArrayType(functionIds)) {
    functionIds = [functionIds];
  }
  functionIds = functionIds.map((functionId) => mongoose.Types.ObjectId(functionId));
  return FunctionVariable.find({ functionId: { $in: functionIds } }).lean();
}

/**
 * 获取函数变量信息
 * @param {String} functionId 函数id
 * @param {boolean} addFunctionId 是否在局部静态变量和桩函数加functionId
 * @return {Promise}
 */
async function getFunctionVariables(functionIds, addFunctionId = undefined) {
  const isArray = utility.isArrayType(functionIds);
  functionIds = [].concat(functionIds).map((id) => mongoose.Types.ObjectId(id));
  const variables = await FunctionVariable.find({ functionId: { $in: functionIds } });
  if (variables && variables.length === 0) {
    return null;
  }
  const funcVariableMap = new Map();
  // 只有集成测试需要@fileId或者@functionId
  if (addFunctionId) {
    variables.forEach((item) => {
      const functionId = item.functionId.toString();
      if (item.variables && item.variables.statics) {
        Object.keys(item.variables.statics).forEach((key) => {
          item.variables.statics[key].functionId = functionId;
        });
      }
      if (item.fixedAddrs && item.fixedAddrs.exprs) {
        Object.keys(item.fixedAddrs.exprs).forEach((key) => {
          // 集成测试参与集成的函数会拼接函数id
          let exprFuncId = functionId;
          const check = handleIntegrationVariables.checkAttachFileIdOrFunctionId(key);
          if (check) {
            [, exprFuncId] = check;
          }
          item.fixedAddrs.exprs[key].functionId = exprFuncId;
        });
      }
      // 集成测试所有检查项输出都拼接functionId
      if (item.output.statics) {
        Object.keys(item.output.statics).forEach((key) => {
          if (!handleIntegrationVariables.checkAttachFileIdOrFunctionId(key)) {
            const newKey = `${key}@${functionId}`;
            item.output.statics[newKey] = item.output.statics[key];
            delete item.output.statics[key];
            key = newKey;
          }
          item.output.statics[key].functionId = functionId;
        });
      }
      funcVariableMap.set(functionId, item);
    });
  }
  if (!isArray) {
    return variables[0];
  }
  return funcVariableMap;
}
async function updateFunctionOriginalVariable(functionId, doc) {
  await FunctionOriginalVariable.deleteMany({ functionId: mongoose.Types.ObjectId(functionId) });
  const functionOriginalVariable = new FunctionOriginalVariable(doc);
  return functionOriginalVariable.save();
}
function getFunctionOriginalVariables(functionIds) {
  return FunctionOriginalVariable.find({ functionId: { $in: functionIds.map((id) => mongoose.Types.ObjectId(id)) } });
}
/**
 * 获取文件的函数变量信息
 * @param {String} fileId 文件id
 * @return {Promise}
 */
function getFunctionVariablesOfFile(fileId) {
  return FunctionVariable.find({ fileId: mongoose.Types.ObjectId(fileId) });
}

/**
 * 获取函数变量json信息
 * @param {Object} functionVariable 函数变量
 * @returns {Object}
 */
function getFunctionVariableJson(functionVariable) {
  const json = {
    functionId: functionVariable.functionId,
    variables: functionVariable.variables,
    stubs: functionVariable.stubs,
    malloc: functionVariable.malloc,
    fixedAddrs: functionVariable.fixedAddrs,
    output: functionVariable.output,
    fileId: functionVariable.fileId,
    objectName: functionVariable.objectName,
  };
  return json;
}

/**
 * 获取函数CFG信息
 * @param {String} functionId 函数id
 * @param {String} entryFuncId 入口函数id
 * @param {Boolean} includeCovered 是否包含覆盖信息
 * @return {Promise}
 */
async function getFunctionCFG(functionId, entryFuncId = null, includeCovered = true) {
  functionId = mongoose.Types.ObjectId(functionId);
  const cfg = await FunctionCFG.findOne({ functionId }).lean();
  if (!cfg) {
    return null;
  }
  if (!includeCovered) {
    return cfg;
  }
  if (!entryFuncId) {
    entryFuncId = functionId;
  }
  const filter = { functionId };
  const testcases = await tests.getTestsOfFunctions(entryFuncId);
  filter.testcaseId = { $in: testcases.map((test) => test._id) };
  const [testcasesCovered, blackBoxCovered, manualCovered] = await Promise.all([
    TestCaseCoveredField.find(filter),
    BlackBoxCovered.find({ functionId }),
    ManualCovered.find({ functionId }),
  ]);
  // merge covered fields into cfg
  function handlePath(testId, path, count) {
    const initUndefinedField = path[0] === 'invokes' || path[2] === 'coveredConditions';
    path.reduce((pre, key, index) => {
      if (!pre[key]) {
        if (!initUndefinedField) {
          return {};
        }
        pre[key] = {};
      }
      if (index === path.length - 1) {
        if (!pre[key].testIds) {
          pre[key].testIds = {};
        }
        pre[key].testIds[testId] = count;
        return {};
      }
      return pre[key];
    }, cfg);
  }
  const decisionCasesMap = {};
  [...testcasesCovered, ...blackBoxCovered, ...manualCovered].forEach((covered) => {
    let testId = covered._id.toString();
    if (covered.testcaseId) {
      testId = covered.testcaseId.toString();
    }
    if (!covered.coveredFields) {
      return;
    }
    Object.keys(covered.coveredFields).forEach((path) => {
      const pathArr = path.split('|');
      handlePath(testId, pathArr, covered.coveredFields[path]);
      // 代码块内显示手动覆盖信息
      if (covered.description && pathArr.length === 2 && (pathArr[0] === 'nodes' || pathArr[0] === 'edges')) {
        utility.setValueByKeyPathArray(cfg, [...pathArr, 'coveredDescription'], covered.description, false);
      }
      if (pathArr[0] !== 'decisions') {
        return;
      }
      const decisionId = Number(pathArr[1]);
      if (!cfg.decisions
        || !cfg.decisions[decisionId]
        || !cfg.decisions[decisionId].cases
      ) {
        return;
      }
      if (!decisionCasesMap[decisionId]) {
        decisionCasesMap[decisionId] = {};
        Object.keys(cfg.decisions[decisionId].cases).forEach((key) => {
          decisionCasesMap[decisionId][cfg.decisions[decisionId].cases[key].condKey] = key;
        });
      }
      if (!decisionCasesMap[decisionId][pathArr[3]]) {
        return;
      }
      const caseId = decisionCasesMap[decisionId][pathArr[3]];
      if (!cfg.decisions[decisionId].cases[caseId].testIds) {
        cfg.decisions[decisionId].cases[caseId].testIds = {};
      }
      // 真值表内显示手动覆盖信息
      if (covered.description) {
        cfg.decisions[decisionId].cases[caseId].coveredDescription = covered.description;
      }
      cfg.decisions[decisionId].cases[caseId].testIds[testId] = covered.coveredFields[path];
    });
  });
  return cfg;
}

// 获取死循环列表
async function getInfiniteLoops(funcId) {
  const cfg = await FunctionCFG.findOne({ functionId: funcId }).lean();
  if (!cfg) {
    return null;
  }
  const { nodes, fileId, functionId } = cfg;
  const variable = await FunctionVariable.findOne({ functionId }).lean();
  const list = Object.values(nodes).filter((node) => node.infiniteLoopEntry).map((node) => {
    const loop = variable && variable.infiniteLoops
      ? variable.infiniteLoops.find((item) => item.functionId.equals(functionId) && item.nodeId === node.nodeId)
      : null;
    return {
      fileId,
      functionId,
      node,
      infiniteLoopEntry: node.infiniteLoopEntry,
      times: loop ? loop.times : null,
    };
  });
  return list;
}

async function setInfiniteLoopTimes(funcId, infiniteLoopInfo) {
  if (!infiniteLoopInfo || !utility.isArrayType(infiniteLoopInfo)) {
    return;
  }
  const funcsIds = infiniteLoopInfo.map((loop) => mongoose.Types.ObjectId(loop.functionId));
  const funcsInfo = await Func.aggregate([
    {
      $match: {
        _id: {
          $in: funcsIds,
        },
      },
    },
    {
      $lookup: {
        from: 'files',
        localField: 'fileId',
        foreignField: '_id',
        as: 'file',
      },
    },
    {
      $unwind: '$file',
    },
    {
      $project: {
        functionName: 1,
        mangledId: 1,
        fileId: 1,
        filePath: '$file.path',
        language: 1,
      },
    },
  ]);
  const funcsMap = new Map();
  funcsInfo.forEach((func) => {
    funcsMap.set(func._id.toString(), func);
  });
  const updateDoc = [];
  infiniteLoopInfo.forEach((loop) => {
    if (!loop.times || !funcsMap.has(loop.functionId.toString())) {
      return;
    }
    const func = funcsMap.get(loop.functionId.toString());
    updateDoc.push({
      times: parseInt(loop.times, 10),
      fileId: func.fileId,
      functionId: loop.functionId,
      nodeId: loop.nodeId,
      filePath: func.filePath,
      // eslint-disable-next-line no-use-before-define
      functionKey: isCFunction(func) ? func.functionName : func.mangledId,
    });
  });

  const { nModified } = await FunctionVariable.updateOne({ functionId: mongoose.Types.ObjectId(funcId) }, { $set: { infiniteLoops: updateDoc } });
  if (nModified) {
    // eslint-disable-next-line no-use-before-define
    await removeResultOfExecution(funcId);
  }
}

/**
 * 获取指针目标变量数据
 * @param {String} functionId 函数id
 * @param {String} variableName 变量名
 * @return {Promise}
 */
function getMallocVariable(functionId, variableName) {
  return new Promise((resolve, reject) => {
    getFunctionVariables(functionId)
      .then((variables) => {
        if (!variables || !variables.malloc) {
          resolve();
        } else {
          resolve(variables.malloc[variableName]);
        }
      })
      .catch(reject);
  });
}

/**
 * 获取桩函数指针目标数据
 * @param {String} functionId 函数id
 * @param {String} stubName 桩函数名
 * @param {String} pointerTargetName 指针目标名
 * @return {Promise}
 */
function getStubPointerTarget(functionId, stubName, pointerTargetName, stubKey = 'stubs') {
  return new Promise((resolve, reject) => {
    getFunctionVariables(functionId)
      .then((variables) => {
        if (!variables || !variables[stubKey]) {
          resolve();
        } else if (!variables[stubKey][stubName] || !variables[stubKey][stubName].pointerTargets) {
          resolve();
        } else {
          resolve(variables[stubKey][stubName].pointerTargets[pointerTargetName]);
        }
      })
      .catch(reject);
  });
}

/**
 * 获取绝对地址
 * @param {String} functionId 函数id
 * @param {String} fixedAddressName 绝对地址名
 * @return {Promise}
 */
function getFixedAddress(functionId, fixedAddressName) {
  return new Promise((resolve, reject) => {
    getFunctionVariables(functionId)
      .then((variables) => {
        if (!variables || !variables.fixedAddrs
          || !variables.fixedAddrs.bases || !fixedAddressName) {
          resolve();
        } else if (!variables.fixedAddrs.bases[fixedAddressName]) {
          resolve();
        } else {
          resolve(variables.fixedAddrs.bases[fixedAddressName]);
        }
      })
      .catch(reject);
  });
}

/**
 * 获取输出检查项中的绝对地址
 * @param {String} functionId 函数id
 * @param {String} fixedAddressName 绝对地址名
 * @return {Promise}
 */
function getOutputFixedAddress(functionId, fixedAddressName) {
  return new Promise((resolve, reject) => {
    getFunctionVariables(functionId)
      .then((variables) => {
        if (!variables || !variables.output || !variables.output.fixedAddrs
          || !fixedAddressName) {
          resolve();
        } else if (!variables.output.fixedAddrs[fixedAddressName]) {
          resolve();
        } else {
          resolve(variables.output.fixedAddrs[fixedAddressName]);
        }
      })
      .catch(reject);
  });
}

/**
 * 获取函数总数
 * @param {String} versionId 项目版本id
 * @param {Object} options 筛选项
 * @return {Promise}
 */
function getCount(versionId, options = {}) {
  options.versionId = mongoose.Types.ObjectId(versionId);
  return Func.countDocuments(options);
}

/**
 * 根据文件后缀名获取函数总数
 * @param {String} versionId 项目版本id
 * @param {Array} extname 筛选项
 * @return {Promise}
 */
async function getCountByExtname(versionId, extname = []) {
  const functionsCount = await Func.aggregate(
    [
      {
        $match: {
          versionId: mongoose.Types.ObjectId(versionId),
          kind: { $in: fileElement.getCountableKinds() },
          $or: [
            { insideProject: { $eq: true } },
            { insideProject: { $exists: 0 } },
          ],
        },
      },
      {
        $group: {
          _id: {
            fileId: '$fileId',
            changed: '$changed',
          },
          count: { $sum: 1 },
        },
      },
      {
        $lookup: {
          from: 'files',
          localField: '_id.fileId',
          foreignField: '_id',
          as: 'file',
        },
      },
      {
        $unwind: '$file',
      },
      {
        $project: {
          count: 1,
          'file.extname': 1,
        },
      },
      {
        $match: { 'file.extname': { $in: extname } },
      },
    ]
  );
  // console.log(functionsCount);
  return functionsCount.reduce((obj, e) => {
    obj.total += e.count;
    if (e._id.changed === 'added') {
      obj.added += e.count;
    } else if (e._id.changed === 'modified') {
      obj.modified += e.count;
    }
    return obj;
  }, {
    total: 0,
    added: 0,
    modified: 0,
  });
}

/**
 * 获取数组类型名
 * @param {String} elementTypeName 元素类型名
 * @param {String|Number} length 数组长度
 * @return {String}
 */
function getArrayTypeName(elementTypeName, length) {
  if (elementTypeName.endsWith(']')) {
    return `${elementTypeName}[${length}]`;
  }
  return `${elementTypeName} [${length}]`;
}

function addStubForConstructor(functionId, typeName, constructorMangleIdName, fileId) {
  if (!constructorMangleIdName) {
    return Promise.resolve();
  }
  let versionId;
  functionId = mongoose.Types.ObjectId(functionId);
  return FunctionVariable.findOne({ functionId })
    .then((funcVariable) => {
      if (funcVariable && funcVariable.stubs && funcVariable.stubs[constructorMangleIdName]) {
        return Promise.resolve();
      }
      return Func.findOne({ _id: functionId })
        .then((func) => {
          versionId = func.versionId;
          return fileModels.getFileTypeSystemByFile(fileId || func.fileId);
        })
        .then((typesInfo) => {
          const typeNames = typeName ? [typeName] : Object.keys(typesInfo.types);
          let functionDecl = null;
          typeNames.forEach((name) => {
            const typeInfo = typesInfo.types[name];
            const constructorsInfo = typeInfo && typeInfo['@constructors'] && utility.isArrayType(typeInfo['@constructors']) ? typeInfo['@constructors'] : [];
            constructorsInfo.forEach((type) => {
              if (type['@mangled'] === constructorMangleIdName) {
                functionDecl = type;
              }
            });
          });
          if (!functionDecl) {
            return Promise.resolve();
          }
          // eslint-disable-next-line no-use-before-define
          if (isDefaulted(functionDecl['@attributes'] || null)) {
            return Promise.resolve();
          }
          const stubData = { $set: {} };
          stubData.$set[`stubs.${constructorMangleIdName}`] = functionDecl;
          stubData.$set[`stubs.${constructorMangleIdName}`].stubsTriggerKind = tests.stubTriggerKind.constructor;
          return FunctionVariable.updateOne({ functionId }, stubData)
            .then(() => invokeRelations.createRelationsForConstructor(
              versionId,
              functionId,
              functionDecl['@mangled'],
              functionDecl['@fullName']
            ));
        });
    });
}

/**
 * 添加指针目标
 * @param {String} functionId 函数id
 * @param {String} variableName 变量名
 * @param {String} typeName 类型名
 * @param {String} elementTypeName 数组单元素类型名
 * @param {Number} length 长度
 * @param {String} constructor 构造函数mangleId
 * @param {String} fileId 文件Id
 * @return {Promise}
 */
async function addMallocVariable(
  functionId,
  {
    variableName,
    typeName = null,
    elementTypeName = null,
    length = null,
    constructor = null,
    fileId = null,
  } // eslint-disable-line comma-dangle
) {
  functionId = mongoose.Types.ObjectId(functionId);
  const doc = { $set: {} };
  if (elementTypeName) {
    typeName = getArrayTypeName(elementTypeName, length);
  }
  doc.$set[`malloc.${variableName}`] = {
    '@type': typeName || undefined,
    '@elementType': elementTypeName || undefined,
    '@length': length ? Number.parseInt(length.toString(), 10) : undefined,
  };
  const func = await Func.findById(functionId);
  if (fileId && func.fileId.toString() !== fileId.toString()) {
    const file = await File.findById(fileId);
    doc.$set[`malloc.${variableName}`].fileId = fileId;
    doc.$set[`malloc.${variableName}`].filePath = file.path;
  }
  let constructorMangleIdName;
  if (constructor && utility.isStringType(constructor)) {
    constructorMangleIdName = constructor;
    if (elementTypeName) {
      doc.$set[`malloc.${variableName}`]['@init'] = { '#': { '@init': { '@ctor': { '@value': constructorMangleIdName } } } };
    } else {
      doc.$set[`malloc.${variableName}`]['@init'] = { '@ctor': { '@value': constructorMangleIdName } };
    }
  }
  return Promise.all(
    [
      FunctionVariable.updateOne({ functionId }, doc),
      addStubForConstructor(functionId, typeName, constructorMangleIdName, fileId),
    ]
  );
}

/**
 * 批量添加指针目标
 * @param {String} functionId 函数id
 * @param {Array} variables 变量集合
 * @return {Promise}
 */
async function addMallocVariables(functionId, variables) {
  functionId = mongoose.Types.ObjectId(functionId);
  const functionVariable = await FunctionVariable.findOne({ functionId });
  const mallocs = functionVariable.malloc;
  const doc = { $set: {} };
  const filesMap = new Map();
  await Promise.all(variables.map(async (variable) => {
    const {
      variableName,
      elementTypeName,
      filePath,
    } = variable;
    let { typeName, length } = variable;
    const key = `malloc.${variableName}`;
    if (length && doc.$set[key] && doc.$set[key].length) {
      length = Number.parseInt(length.toString(), 10);
      if (length < doc.$set[key].length) {
        length = doc.$set[key].length;
      }
    }
    if (elementTypeName) {
      if (elementTypeName.endsWith(']')) {
        typeName = `${elementTypeName}[${length}]`;
      } else {
        typeName = `${elementTypeName} [${length}]`;
      }
    }
    doc.$set[key] = {
      '@type': typeName || undefined,
      '@elementType': elementTypeName || undefined,
      '@length': length ? Number.parseInt(length.toString(), 10) : undefined,
    };
    if (filePath) {
      const file = filesMap.get(filePath) || (await File.findOne({ path: filePath, versionId: functionVariable.versionId }, ['_id', 'path']));
      if (!file) {
        delete doc.$set[key];
        return;
      }
      filesMap.set(filePath, file);
      if (file._id.toString() !== functionVariable.fileId.toString()) {
        // 已选择文件的指针保留指针目标最新的文件防止类型丢失
        if (mallocs && mallocs[variableName]) {
          doc.$set[key].fileId = mallocs[variableName].fileId;
          doc.$set[key].filePath = mallocs[variableName].filePath;
        } else { // 新导入的指针使用导入的文件
          doc.$set[key].fileId = file._id.toString();
          doc.$set[key].filePath = file.path;
        }
      }
    }
  }));
  return FunctionVariable.updateOne({ functionId }, doc);
}

/**
 * 添加桩函数指针目标
 * @param {String} functionId 函数id
 * @param {String} stubName 桩函数名
 * @param {String} pointerTargetName 指针目标名
 * @param {String} typeName 类型名
 * @param {String} elementTypeName 数组单元素类型名
 * @param {Number} length 长度
 * @param {String} stubKey 桩函数键值
 * @return {Promise}
 */
function addStubPointerTarget(
  functionId,
  stubName,
  {
    pointerTargetName,
    typeName = null,
    elementTypeName = null,
    length = null,
  },
  stubKey = 'stubs',
) {
  functionId = mongoose.Types.ObjectId(functionId);
  const doc = { $set: {} };
  if (elementTypeName) {
    typeName = getArrayTypeName(elementTypeName, length);
  }
  doc.$set[`${stubKey}.${stubName}.pointerTargets.${pointerTargetName}`] = {
    '@type': typeName || undefined,
    '@elementType': elementTypeName || undefined,
    '@length': length ? Number.parseInt(length.toString(), 10) : undefined,
  };
  return FunctionVariable.updateOne({ functionId }, doc);
}

/**
 * 添加绝对地址，添加到绝对地址合集
 * @param {String} functionId 函数Id
 * @param {String} fixedAddressName 绝对地址名
 * @param {String} length 字节长度
 * @param {Object} pointerTargets 指针目标
 * @param {String} fileId 绝对地址目标来源文件
 * @param {Number} assignmentKind 绝对地址目标类型 1：偏移量 2：表达式
 * @return {Promise}
 */
async function addFixedAddressBases(
  functionId,
  fixedAddressName,
  length = null,
  pointerTargets = undefined,
  fileId,
  assignmentKind = fixedAddrBasesAssignmentKind.baseOffset
) {
  const doc = { $set: {} };
  const func = await Func.findById(functionId);
  if (!func) {
    return;
  }
  if (pointerTargets) {
    Object.keys(pointerTargets).forEach((key) => {
      doc.$set[`fixedAddrs.bases.${fixedAddressName}.pointerTargets.${key}`] = pointerTargets[key];
    });
  }
  if (!assignmentKind || assignmentKind === fixedAddrBasesAssignmentKind.baseOffset) {
    doc.$set[`fixedAddrs.bases.${fixedAddressName}.@length`] = length;
  }
  doc.$set[`fixedAddrs.bases.${fixedAddressName}.@hex`] = utility.convertDecToHex(fixedAddressName);
  doc.$set[`fixedAddrs.bases.${fixedAddressName}.@dec`] = fixedAddressName;
  doc.$set[`fixedAddrs.bases.${fixedAddressName}.assignmentKind`] = assignmentKind;
  if (fileId && func.fileId.toString() !== fileId.toString()) {
    const file = await File.findById(fileId);
    if (file) {
      doc.$set[`fixedAddrs.bases.${fixedAddressName}.fileId`] = fileId;
      doc.$set[`fixedAddrs.bases.${fixedAddressName}.filePath`] = file.path;
    }
  }
  await FunctionVariable.updateOne({ functionId }, doc);
  // 测试用例绝对地址表达式默认赋值
  if (assignmentKind && assignmentKind === fixedAddrBasesAssignmentKind.baseExprs) {
    await TestCase.updateMany({ functionId }, { $set: { [`data.fixedAddrs.bases.${fixedAddressName}.pointerTargets`]: pointerTargets } });
  }
}

/**
 * 添加绝对地址，添加到表达式合集
 * @param {String} functionId
 * @param {Array<String>} exprsName
 * @return {Promise}
 */
function addFixedAddressExprs(
  functionId,
  exprsName,
) {
  if (!exprsName || exprsName.length === 0) {
    return Promise.resolve();
  }
  functionId = mongoose.Types.ObjectId(functionId);
  const doc = { $set: {} };
  exprsName.forEach((expr) => {
    doc.$set[`fixedAddrs.exprs.${utility.toFullWidth(expr)}.enabled`] = 1;
  });
  return FunctionVariable.updateOne({ functionId }, doc);
}

/**
 * 添加桩指针目标
 * @param {String} functionId 函数id
 * @param {Array} pointerTargets 指针目标集合
 * @return {Promise}
 */
function addStubPointerTargets(functionId, pointerTargets) {
  functionId = mongoose.Types.ObjectId(functionId);
  const doc = { $set: {} };
  pointerTargets.forEach((pointerTarget) => {
    const key = `stubs.${pointerTarget.stubName}.pointerTargets.${pointerTarget.variableName}`;
    let { length } = pointerTarget;
    if (length) {
      doc.$set[key] = doc.$set[key] || { '@length': 0 };
      length = Number.parseInt(length, 10);
      if (length < doc.$set[key]['@length']) {
        length = doc.$set[key]['@length'];
      }
    }
    if (pointerTarget.elementTypeName) {
      if (pointerTarget.elementTypeName.endsWith(']')) {
        pointerTarget.typeName = `${pointerTarget.elementTypeName}[${length}]`;
      } else {
        pointerTarget.typeName = `${pointerTarget.elementTypeName} [${length}]`;
      }
    }
    doc.$set[key] = {
      '@type': pointerTarget.typeName || undefined,
      '@elementType': pointerTarget.elementTypeName || undefined,
      '@length': length || undefined,
    };
  });
  return FunctionVariable.updateOne({ functionId }, doc);
}

/**
 * 设置全局变量检查项
 * @param {String} functionId 函数id
 * @param {String} variableName 变量名
 * @param {Object} variableInfo 变量信息
 * @return {Promise}
 */
async function addOutputGlobalVariable(functionId, variableName, variableInfo, fileId = undefined) {
  functionId = mongoose.Types.ObjectId(functionId);
  const doc = { $set: {} };
  doc.$set[`output.global.${variableName}`] = variableInfo;
  if (fileId) {
    const file = await File.findById(fileId);
    if (file) {
      await TestCase.updateMany({ functionId }, {
        $set: {
          [`data.output.global.${variableName}`]: {
            fileId: file._id.toString(),
            filePath: file.path,
          },
        },
      });
    }
  }
  await FunctionVariable.updateOne({ functionId }, doc);
}

/**
 * 设置静态变量检查项
 * @param {String} functionId 函数id
 * @param {String} index 变量下标
 * @param {Object} variableInfo 变量信息
 * @return {Promise}
 */
function addOutputStaticsVariable(functionId, index, variableInfo) {
  functionId = mongoose.Types.ObjectId(functionId);
  const doc = { $set: {} };
  doc.$set[`output.statics.${index}`] = variableInfo;
  return FunctionVariable.updateOne({ functionId }, doc);
}

/**
 * 设置绝对地址检查项
 * @param {String} functionId 函数id
 * @param {String} variableName 变量名
 * @param {Object} variableInfo 变量信息
 * @return {Promise}
 */
async function addOutputFixedAddress(functionId, variableName, variableInfo) {
  functionId = mongoose.Types.ObjectId(functionId);
  const doc = { $set: {} };
  doc.$set[`output.fixedAddrs.${variableName}`] = variableInfo;
  return FunctionVariable.updateOne({ functionId }, doc);
}

/**
 * 设置指针目标检查项
 * @param {String} functionId 函数id
 * @param {String} variableName 变量名
 * @param {Object} variableInfo 变量信息
 * @return {Promise}
 */
function addOutputMallocVariable(functionId, variableName, variableInfo) {
  functionId = mongoose.Types.ObjectId(functionId);
  const doc = { $set: {} };
  doc.$set[`output.malloc.${variableName}`] = variableInfo;
  return FunctionVariable.updateOne({ functionId }, doc);
}

/**
 * 设置成员函数所属类检查项
 * @param {String} functionId 函数id
 * @return {Promise}
 */
function addOutputObjectVariable(functionId) {
  functionId = mongoose.Types.ObjectId(functionId);
  const doc = { $set: {} };
  doc.$set['output.object'] = { '@type': '', '@init': {}, '@value': {} };
  return FunctionVariable.updateOne({ functionId }, doc);
}

/**
 * 批量添加指针目标输出项
 * @param {String} functionId 函数id
 * @param {Array} mallocOutputVariables 变量集合
 * @return {Promise}
 */
function addOutputMallocVariables(functionId, mallocOutputVariables) {
  functionId = mongoose.Types.ObjectId(functionId);
  const doc = { $set: {} };
  mallocOutputVariables.forEach((variable) => {
    const key = `output.malloc.${variable.variableName}`;
    let { length } = variable;
    if (length && doc.$set[key] && doc.$set[key].length) {
      length = Number.parseInt(length.toString(), 10);
      if (length < doc.$set[key].length) {
        length = doc.$set[key].length;
      }
    }
    if (variable.elementTypeName) {
      if (variable.elementTypeName.endsWith(']')) {
        variable.typeName = `${variable.elementTypeName}[${length}]`;
      } else {
        variable.typeName = `${variable.elementTypeName} [${length}]`;
      }
    }
    doc.$set[key] = {
      '@type': variable.typeName || undefined,
      '@elementType': variable.elementTypeName || undefined,
      '@length': length ? Number.parseInt(length.toString(), 10) : undefined,
    };
  });
  return FunctionVariable.updateOne({ functionId }, doc);
}

function addOutputIntegratedFunc(functionId, mangledId) {
  functionId = mongoose.Types.ObjectId(functionId);
  const doc = { $set: {} };
  doc.$set[`output.${integratedFuncKey}.${mangledId}`] = {};
  return FunctionVariable.updateOne({ functionId }, doc);
}

async function getExternalGLobalOptionsList(functionId, {
  q,
} = {}) {
  functionId = mongoose.Types.ObjectId(functionId);
  const func = await Func.findById(functionId);
  return projectVersions.getGlobalList(func.versionId, { functionId, q });
}

async function replaceExternalGlobals(functionId, items, {
  replace = true,
} = {}) {
  items = items.map(({ fileId, fullname }) => {
    if (!fileId || !fullname) {
      throw new PhoenixError('invalid request', 'need fileId and fullname');
    }
    return { fileId: mongoose.Types.ObjectId(fileId), fullname };
  });
  const pipeline = [];
  if (replace) {
    pipeline.push({
      // 清空已存在的值
      $set: { externalGlobals: [] },
    });
  }
  // 去重
  pipeline.push(
    {
      $set: { externalGlobals: { $concatArrays: ['$externalGlobals', items] } },
    },
    {
      $set: {
        externalGlobals: {
          $arrayToObject: {
            $map: {
              input: '$externalGlobals',
              as: 'item',
              in: [{ $concat: ['$$item.fullname', '@', { $toString: '$$item.fileId' }] }, '$$item'],
            },
          },
        },
      },
    },
    { $set: { externalGlobals: { $objectToArray: '$externalGlobals' } } },
    { $set: { externalGlobals: '$externalGlobals.v' } }
  );
  const { nModified } = await FunctionVariable
    .updateOne({ functionId: mongoose.Types.ObjectId(functionId) }, pipeline);
  if (nModified) {
    // eslint-disable-next-line no-use-before-define
    await removeResultOfExecution(functionId);
  }
}

/**
 * 获取边键值
 * @param {String} startNodeId 起始节点id
 * @param {String} endNodeId 终止节点id
 * @return {String}
 */
function getEdgeKey(startNodeId, endNodeId) {
  return `${startNodeId}->${endNodeId}`;
}

/**
 * 获取分支数据结构
 * @param {Array} branches 分支数据
 * @return {Object}
 */
function getBranches(branches) {
  const result = {};
  branches.forEach((branch) => {
    result[branch.id.toString()] = {
      nodeId: branch.block_id,
      decisionId: branch.decision_id,
      branches: {},
    };
    branch.succs.forEach((item) => {
      const testIds = {};
      if (item.testcases) {
        item.testcases.forEach((testId) => {
          testIds[testId] = item.count;
        });
      }
      let key = item.block.toString();
      if (item.sentence !== undefined) {
        key = `${key}-${item.sentence.toString()}`;
      }
      result[branch.id.toString()].branches[key] = {
        testIds, kind: item.kind, value: item.value,
      };
    });
  });
  return result;
}

function getCoveredConditions(decision) {
  if (!decision.evaluates) {
    return {};
  }
  const coveredConditions = {};
  decision.evaluates.forEach((evaluate) => {
    if (evaluate.count <= 0 || !evaluate.conditions || !evaluate.testcases) {
      return;
    }
    const key = [];
    const conditionMap = new Map();
    evaluate.conditions.forEach((cond) => {
      conditionMap.set(cond.id, Number(cond.value));
    });
    for (let i = 0; i < decision.conditions.length; i += 1) {
      if (conditionMap.has(i)) {
        key.push(conditionMap.get(i));
      } else {
        // 短路
        key.push('x');
      }
    }
    // 拼接最终结果
    key.push(Number(evaluate.return));
    if (key.length === 0) {
      return;
    }
    const keyStr = key.join('');
    if (!coveredConditions[keyStr]) {
      coveredConditions[keyStr] = { testIds: {} };
    }
    evaluate.testcases.forEach((testId) => {
      coveredConditions[key.join('')].testIds[testId] = evaluate.count;
    });
  });
  return coveredConditions;
}

function getMcdcCases(conditions, coveredConditions, mcdcKind, caseIncludeTestId = false) {
  const conditionsMap = {};
  conditions.forEach((condition) => {
    conditionsMap[condition.id] = {
      t: condition.T_out === 'TRUE' ? true : (condition.T_out === 'FALSE' ? false : Number(condition.T_out)), // eslint-disable-line no-nested-ternary
      f: condition.F_out === 'TRUE' ? true : (condition.F_out === 'FALSE' ? false : Number(condition.F_out)), // eslint-disable-line no-nested-ternary
    };
  });
  // 生成全量真值表
  const fullTruthTable = { t: [], f: [] };
  const condLen = conditions.length;
  function resc(id, arr, fullTruthTable) {
    const arrCopy = [...arr];
    let arrPoint;
    ['t', 'f'].forEach((sign) => {
      if (sign === 't') {
        arrPoint = arr;
        arrPoint[id] = 1;
      } else {
        arrPoint = arrCopy;
        arrPoint[id] = 0;
      }
      if (conditionsMap[id][sign] === true) {
        fullTruthTable.t.push(arrPoint);
      } else if (conditionsMap[id][sign] === false) {
        fullTruthTable.f.push(arrPoint);
      } else {
        resc(conditionsMap[id][sign], arrPoint, fullTruthTable);
      }
    });
  }
  resc(condLen - 1, Array.from(Array(condLen)), fullTruthTable);
  // 位置与覆盖组合的关系
  const conditionTruthPairMap = {};
  // 条件与位置的关系
  const truthPairConditionMap = {};
  // 返回为true的组合的统计
  const tcount = {};
  // 返回为false的组合的统计
  const fcount = {};
  let truthKey = '';
  // 最后需要生成的真值表，算法一是全量真值表的子集，算法二等于全量真值表
  const truthTable = { t: {}, f: {} };
  let conditionCoveredPairs = {};
  let skip = false;
  const falseGroup = fullTruthTable.f.map((f, j) => ({
    f,
    j,
    falseKey: `${f.map((i) => (i === undefined ? 'x' : i)).join('')}0`,
  }));
  const hugeData = fullTruthTable.t.length > 10000 || fullTruthTable.f.length > 10000;
  const coveredFalseGroup = falseGroup.filter(({ falseKey }) => coveredConditions[falseKey]);
  fullTruthTable.t.forEach((t, i) => {
    truthKey = `${t.map((i) => (i === undefined ? 'x' : i)).join('')}1`;
    // 正常情况下需要遍历所有的返回结果为false的条件组合
    let falseTable = falseGroup;
    // 需要跳过的情况下并且用例未覆盖的返回结果为true的条件组合，只要遍历用例已覆盖的返回结果为false的条件组合
    if (skip && !coveredConditions[truthKey]) {
      falseTable = coveredFalseGroup;
    }
    const coveredPosition = {};
    falseTable.forEach(({ f, j, falseKey }) => {
      let position = null;
      for (let k = 0; k < condLen; k += 1) {
        if (t[k] === undefined || f[k] === undefined || t[k] === f[k]) {
          // eslint-disable-next-line no-continue
          continue;
        }
        if (position !== null) {
          return;
        }
        position = k;
      }
      if (position === null) {
        return;
      }
      // 只有一位相异
      if (hugeData && coveredPosition[position]) {
        return;
      }
      coveredPosition[position] = true;
      if (!conditionTruthPairMap[position]) {
        conditionTruthPairMap[position] = [];
        // 当每个条件都获取到覆盖组合并且真值表过大的情况下，考虑全部两两遍历太耗时，采取跳过的策略
        skip = hugeData && condLen === Object.keys(conditionTruthPairMap).length;
      }
      if (!tcount[i]) {
        tcount[i] = { c: 0, pair: {} };
      }
      if (!fcount[j]) {
        fcount[j] = { c: 0, pair: {} };
      }
      if (!tcount[i].pair[position]) {
        tcount[i].pair[position] = j;
        tcount[i].c += 1;
      }
      if (!fcount[j].pair[position]) {
        fcount[j].pair[position] = i;
        fcount[j].c += 1;
      }
      conditionTruthPairMap[position].push([i, j]);
      truthPairConditionMap[`${i}|${j}`] = position;
      // 已经覆盖了的处理
      if (coveredConditions[truthKey]) {
        truthTable.t[i] = coveredConditions[truthKey];
        // 已经执行覆盖的增加权重
        tcount[i].c += 5;
      }
      if (coveredConditions[falseKey]) {
        truthTable.f[j] = coveredConditions[falseKey];
        // 已经执行覆盖的增加权重
        fcount[j].c += 5;
      }
      if (coveredConditions[truthKey] && coveredConditions[falseKey]) {
        if (!conditionCoveredPairs[position]) {
          conditionCoveredPairs[position] = [];
        }
        conditionCoveredPairs[position].push([i, j]);
      }
    });
  });
  const cases = {};
  // 算法一
  if (mcdcKind === tests.mcdcKind.masking) {
    // 排序算法，权重大的排前面
    Object.values(conditionTruthPairMap).forEach((combines) => {
      combines.sort((a, b) => ((tcount[b[0]] ? tcount[b[0]].c : 0) + (fcount[b[1]] ? fcount[b[1]].c : 0))
        - ((tcount[a[0]] ? tcount[a[0]].c : 0) + (fcount[a[1]] ? fcount[a[1]].c : 0)));
    });
    for (let i = 0; i < condLen; i += 1) {
      // 所有位置已经覆盖
      if (Object.keys(conditionCoveredPairs).length === condLen) {
        break;
      }
      // 当前位置已经覆盖
      if (conditionCoveredPairs[i]) {
        continue; // eslint-disable-line no-continue
      }
      // 没有覆盖该位置的组合
      if (!conditionTruthPairMap[i]) {
        continue; // eslint-disable-line no-continue
      }
      // 取第一个, 推荐算法会将最优放数组第一位
      const [[t, f]] = conditionTruthPairMap[i];
      conditionCoveredPairs[i] = [[t, f]];
      if (!truthTable.t[t]) {
        truthTable.t[t] = true;
        // 如果是新增的true项，需要与所有false项组合查看是否有新覆盖
        // eslint-disable-next-line no-loop-func
        Object.keys(truthTable.f).forEach((ff) => {
          ff = Number(ff);
          // 如果组合项能覆盖一个位置，则设置该位置状态为已覆盖
          if (!truthPairConditionMap[`${t}|${ff}`] || f === ff) {
            return;
          }
          const position = truthPairConditionMap[`${t}|${ff}`];
          if (!conditionCoveredPairs[position]) {
            conditionCoveredPairs[position] = [];
          }
          conditionCoveredPairs[position].push([t, ff]);
        });
      }
      if (!truthTable.f[f]) {
        truthTable.f[f] = true;
        // 如果是新增的false项，需要与所有true项组合查看是否有新覆盖
        // eslint-disable-next-line no-loop-func
        Object.keys(truthTable.t).forEach((tt) => {
          tt = Number(tt);
          if (!truthPairConditionMap[`${tt}|${f}`] || t === tt) {
            return;
          }
          const position = truthPairConditionMap[`${tt}|${f}`];
          if (!conditionCoveredPairs[position]) {
            conditionCoveredPairs[position] = [];
          }
          conditionCoveredPairs[position].push([tt, f]);
        });
      }
    }
    let loop = 0;
    const caseIdMap = { t: {}, f: {} };
    ['t', 'f'].forEach((tof) => {
      Object.keys(truthTable[tof]).forEach((key) => {
        key = Number(key);
        caseIdMap[tof][key] = loop;
        cases[loop] = {
          return: tof === 't',
        };
        if (caseIncludeTestId && utility.isObjectType(truthTable[tof][key]) && truthTable[tof][key].testIds) {
          if (!cases[loop].testIds) {
            cases[loop].testIds = {};
          }
          cases[loop].testIds = truthTable[tof][key].testIds;
        }
        const conds = [];
        const condKey = [];
        fullTruthTable[tof][key].forEach((item, index) => {
          if (item === undefined) {
            condKey.push('x');
            return;
          }
          condKey.push(item);
          conds.push({
            id: index,
            value: !!item,
          });
        });
        condKey.push(tof === 't' ? '1' : '0');
        cases[loop].conditions = conds;
        cases[loop].condKey = condKey.join('');
        loop += 1;
      });
    });
    Object.keys(conditionCoveredPairs).forEach((key) => {
      conditionCoveredPairs[key] = conditionCoveredPairs[key].map(([i, j]) => [caseIdMap.t[i], caseIdMap.f[j]]);
    });
    // 算法二
  } else {
    let loop = 0;
    ['t', 'f'].forEach((tof) => {
      fullTruthTable[tof].forEach((conditionResults, index) => {
        cases[loop] = {
          return: tof === 't',
        };
        if (caseIncludeTestId && utility.isObjectType(truthTable[tof][index]) && truthTable[tof][index].testIds) {
          if (!cases[loop].testIds) {
            cases[loop].testIds = {};
          }
          cases[loop].testIds = truthTable[tof][index].testIds;
        }
        const conds = [];
        const condKey = [];
        conditionResults.forEach((item, i) => {
          if (item === undefined) {
            condKey.push('x');
            return;
          }
          condKey.push(item);
          conds.push({
            id: i,
            value: !!item,
          });
        });
        condKey.push(tof === 't' ? '1' : '0');
        cases[loop].conditions = conds;
        cases[loop].condKey = condKey.join('');
        loop += 1;
      });
    });
    conditionCoveredPairs = conditionTruthPairMap;
    const trueCount = Object.keys(fullTruthTable.t).length;
    // eslint-disable-next-line no-unused-vars
    Object.keys(conditionCoveredPairs).forEach((key) => {
      conditionCoveredPairs[key] = conditionCoveredPairs[key].map(([i, j]) => [i, j + trueCount]);
    });
  }
  return { cases, conditionCoveredPairs };
}

/**
 * 获取MC/DC数据结构
 * @param {Array} decisions MC/DC判定数据
 * @param {Number} mcdcKind mcdc算法
 * @return {Object}
 */
function getDecisions(decisions, mcdcKind = tests.mcdcKind.masking) {
  const result = {};
  decisions.forEach((decision) => {
    const conditions = {};
    const conditionsInfo = {};
    decision.conditions.forEach((condition) => {
      conditions[condition.id.toString()] = condition.expr;
      conditionsInfo[condition.id.toString()] = {
        begin: condition.begin, end: condition.end, F_out: condition.F_out, T_out: condition.T_out,
      };
    });
    const { cases, conditionCoveredPairs } = getMcdcCases(decision.conditions, getCoveredConditions(decision), mcdcKind);
    result[decision.id.toString()] = {
      nodeId: decision.block_id,
      expr: decision.expr,
      conditions,
      cases,
      conditionCoveredPairs,
      conditionsInfo,
      begin: {
        line: decision.begin.line,
        column: decision.begin.column,
      },
      end: {
        line: decision.end.line,
        column: decision.end.column,
      },
    };
  });
  return result;
}

async function updateMcdcCases(functionId, mcdcKind = tests.mcdcKind.masking) {
  const functionCfg = await getFunctionCFG(functionId);
  if (!functionCfg.decisions) {
    return;
  }
  const update = { $set: {} };
  Object.keys(functionCfg.decisions).forEach((decisionId) => {
    const conditions = [];
    if (functionCfg.decisions[decisionId].conditionsInfo) {
      Object.keys(functionCfg.decisions[decisionId].conditionsInfo).forEach((conditionId) => {
        conditions.push({ ...functionCfg.decisions[decisionId].conditionsInfo[conditionId], id: conditionId });
      });
    }
    const { cases, conditionCoveredPairs } = getMcdcCases(
      conditions,
      functionCfg.decisions[decisionId].coveredConditions || {},
      mcdcKind
    );
    update.$set[`decisions.${decisionId}.cases`] = cases;
    update.$set[`decisions.${decisionId}.conditionCoveredPairs`] = conditionCoveredPairs;
  });
  await FunctionCFG.updateOne({ functionId: mongoose.Types.ObjectId(functionId) }, update);
}

/**
 * 获取CFG结构
 * @param {Array} blocks 块信息
 * @param {Array} branches 分支信息
 * @param {Array} decisions MC/DC信息
 * @param {Number} mcdcKind MC/DC类型
 * @return {Object}
 */
function getCFG(blocks, branches, decisions, mcdcKind) {
  const cfg = {
    nodes: {},
    edges: {},
    decisions: getDecisions(decisions, mcdcKind),
    branches: getBranches(branches),
    goto: 0,
    loopDepthMax: 0,
    loopCount: 0,
  };
  let preExitNodes = [];
  blocks.forEach((block) => {
    let kind = cfgNodeKind.basic;
    switch (block.kind) {
      case 'ENTRY':
        kind = cfgNodeKind.entry;
        break;
      case 'EXIT':
        preExitNodes = block.preds;
        kind = cfgNodeKind.exit;
        break;
      case 'IF':
        kind = cfgNodeKind.conditionIf;
        break;
      case 'FOR':
        kind = cfgNodeKind.conditionFor;
        break;
      case 'WHILE':
        kind = cfgNodeKind.conditionWhile;
        break;
      case 'DOWHILE':
        kind = cfgNodeKind.conditionDoWhile;
        break;
      case 'SWITCH':
        kind = cfgNodeKind.conditionSwitch;
        break;
      case 'RANGE':
        kind = cfgNodeKind.conditionRange;
        break;
      case 'RETURN':
        kind = cfgNodeKind.returnStat;
        break;
      default:
        break;
    }
    cfg.nodes[block.id.toString()] = {
      nodeId: block.id,
      kind,
      label: block.id.toString(),
      testIds: {},
      begin: {
        line: block.begin.line,
        column: block.begin.column,
      },
      end: {
        line: block.end.line,
        column: block.end.column,
      },
      statements: block.statements.reduce((obj, item) => {
        const { begin, end, id } = item;
        obj[id] = {
          begin,
          end,
        };
        return obj;
      }, {}),
    };
    if (block.infiniteLoopEntry) {
      cfg.nodes[block.id.toString()].infiniteLoopEntry = block.infiniteLoopEntry;
    }
    block.preds.forEach((preId) => {
      cfg.edges[getEdgeKey(preId, block.id)] = {
        startNodeId: preId,
        endNodeId: block.id,
        label: '',
        testIds: {},
      };
    });
    block.succs.forEach((s) => {
      if (s.kind === 'GOTO') {
        cfg.goto += 1;
      }
    });
    if (block.loopDepth) {
      block.loopDepth = parseInt(block.loopDepth, 10);
      cfg.loopDepthMax = Math.max(block.loopDepth, cfg.loopDepthMax);
      cfg.loopCount += 1;
    }
  });
  preExitNodes.forEach((nodeId) => {
    if (cfg.nodes[nodeId]) {
      cfg.nodes[nodeId].exitPreNode = true;
    }
  });
  return cfg;
}

function getComplexity(cfg) {
  const edgesKeys = Object.keys(cfg.edges);
  const cycleComplexity = edgesKeys.length - Object.keys(cfg.nodes).length + 2;
  let essentialComplexity = cycleComplexity;
  const edgesMap = new Map();
  edgesKeys.forEach((key) => {
    const [from, to] = key.split('->');
    if (!edgesMap.has(from)) {
      edgesMap.set(from, new Set());
    }
    edgesMap.get(from).add(to);
  });
  edgesKeys.forEach((key) => {
    const [from, to] = key.split('->');
    // 当前只判断if的情况
    if (!cfg.nodes[from]
      || cfg.nodes[from].kind !== cfgNodeKind.conditionIf
      || !cfg.nodes[to]
      || cfg.nodes[to].kind !== cfgNodeKind.conditionIf) {
      return;
    }
    let merge = false;
    edgesMap.get(from).forEach((subTo) => {
      // 两相邻菱形节点有相同的子节点，这两个节点可以化简
      if (cfg.edges[`${to}->${subTo}`]) {
        merge = true;
      }
    });
    if (merge) {
      essentialComplexity -= 1;
    }
  });
  if (essentialComplexity <= 0) {
    return 1;
  }
  return { cycleComplexity, essentialComplexity };
}

/**
 * converedConditions 结构如下
 * {
 *   “0100”: {
 *     testIds:{
 *       "64ae4bee6fb51aa669bbc831": true,
 *       "64ae4bee6fb51aa669bbc832": true
 *     }
 *   },
 *   "01x1": {
 *     testIds:{
 *       "64ae4bee6fb51aa669bbc831": true,
 *       "64ae4bee6fb51aa669bbc832": true
 *     }
 *   }
 * }
 * 0100从左至右依次表示
 * id为0的condition取值为false
 * id为1的condition取值为true
 * id为2的condition取值为false
 * 最后一位表示最终结果为false
 * 其中x表示短路
 * @param coveredConditions
 * @returns {{}}
 */
function calConditionMcdcCovered(coveredConditions) {
  // 覆盖计算
  const coveredMcdc = {};
  const conditionResult = Object.keys(coveredConditions);
  // 最后一位是结果，需要排除
  const conditionLength = conditionResult[0].length - 1;
  for (let i = 0; i < conditionResult.length - 1; i += 1) {
    for (let j = i + 1; j < conditionResult.length; j += 1) {
      // 最后结果相同则忽略
      if (conditionResult[i][conditionLength] === conditionResult[j][conditionLength]) {
        // eslint-disable-next-line no-continue
        continue;
      }
      // 将短路情况进行处理
      let position = null;
      for (let k = 0; k < conditionLength; k += 1) {
        if (conditionResult[i][k] === 'x' || conditionResult[j][k] === 'x' || conditionResult[i][k] === conditionResult[j][k]) {
          // eslint-disable-next-line no-continue
          continue;
        }
        if (position !== null) {
          position = null;
          break;
        }
        position = k;
      }
      if (position === null) {
        // eslint-disable-next-line no-continue
        continue;
      }
      // 只有一位相异
      if (!coveredMcdc[position]) {
        coveredMcdc[position] = [];
      }
      coveredMcdc[position].push({
        pairs: [conditionResult[i], conditionResult[j]],
        testIds: [
          Object.keys(coveredConditions[conditionResult[i]].testIds),
          Object.keys(coveredConditions[conditionResult[j]].testIds),
        ],
      });
    }
  }
  return coveredMcdc;
}
/**
 * 获取覆盖数据
 * @param {Object} nodes 节点集合
 * @param {Object} branches 分支集合
 * @param {Object} decisions MC/DC数据集合
 * @return {Object}
 */
function getCoverage({ nodes = {}, branches = {}, decisions = {} } = {}, mcdcKind = tests.mcdcKind.masking) {
  const nodeIds = Object.keys(nodes);
  let statementTotal = 0;
  let branchTotal = 0;
  let statementCovered = 0;
  let branchCovered = 0;
  let mcdcCovered = 0;
  let mcdcTotal = 0;
  let conditionCovered = 0;
  let conditionTotal = 0;
  let decisionCovered = 0;
  let decisionTotal = 0;

  const testsCovered = {};
  const testCoveredDefault = {
    statementCovered: 0,
    branchCovered: 0,
    mcdcCovered: 0,
    conditionCovered: 0,
    decisionCovered: 0,
  };

  // 语句覆盖
  nodeIds.forEach((nodeId) => {
    const { kind, statements } = nodes[nodeId];
    const statementIds = statements ? Object.keys(statements) : [];
    switch (kind) {
      case cfgNodeKind.entry:
      case cfgNodeKind.exit:
        break;
      default:
        if (statementIds.length) {
          statementTotal += statementIds.length;
          statementIds.forEach((statementId) => {
            const statement = statements[statementId];
            if (statement.testIds && Object.keys(statement.testIds).length > 0) {
              statementCovered += 1;
              Object.keys(statement.testIds).forEach((testId) => {
                testsCovered[testId] = testsCovered[testId] || ({ ...testCoveredDefault });
                testsCovered[testId].statementCovered += 1;
              });
            }
          });
        }
        break;
    }
  });

  // 分支覆盖
  Object.keys(branches).forEach((branchId) => {
    Object.keys(branches[branchId].branches).forEach((block) => {
      branchTotal += 1;
      const { testIds } = branches[branchId].branches[block];
      if (testIds && Object.keys(testIds).length > 0) {
        branchCovered += 1;
        Object.keys(testIds).forEach((testId) => {
          testsCovered[testId] = testsCovered[testId] || ({ ...testCoveredDefault });
          testsCovered[testId].branchCovered += 1;
        });
      }
    });
  });

  // 决策覆盖
  // 节点判断部分
  nodeIds.forEach((nodeId) => {
    const { testIds, kind, exitPreNode } = nodes[nodeId];
    let decisionStatistic = false;
    // 入口节点算或者所有连接结束节点的节点
    if (kind === cfgNodeKind.entry || exitPreNode) {
      decisionStatistic = true;
    }
    if (decisionStatistic) {
      decisionTotal += 1;
      if (testIds && Object.keys(testIds).length > 0) {
        decisionCovered += 1;
        Object.keys(testIds).forEach((testId) => {
          testsCovered[testId] = testsCovered[testId] || ({ ...testCoveredDefault });
          testsCovered[testId].decisionCovered += 1;
        });
      }
    }
  });
  // switch判断部分
  Object.keys(branches).forEach((branchId) => {
    const node = nodes[branches[branchId].nodeId];
    if (!node || node.kind !== cfgNodeKind.conditionSwitch) {
      return;
    }
    decisionTotal += Object.keys(branches[branchId].branches).length;
    Object.keys(branches[branchId].branches).forEach((block) => {
      const { testIds } = branches[branchId].branches[block];
      if (testIds && Object.keys(testIds).length > 0) {
        decisionCovered += 1;
        Object.keys(testIds).forEach((testId) => {
          testsCovered[testId] = testsCovered[testId] || ({ ...testCoveredDefault });
          testsCovered[testId].decisionCovered += 1;
        });
      }
    });
  });
  // decisions判断部分
  Object.keys(decisions).forEach((decisionId) => {
    decisionTotal += 2;
    const { coveredConditions } = decisions[decisionId];
    if (coveredConditions) {
      const coveredDecisionsResultMap = {};
      const testcaseCoveredDecisions = {};
      Object.keys(coveredConditions).forEach((conditionResult) => {
        // 最后一位为decision输出结果
        const result = conditionResult[conditionResult.length - 1];
        if (coveredDecisionsResultMap[result] === undefined) {
          coveredDecisionsResultMap[result] = true;
          decisionCovered += 1;
        }
        const { testIds } = coveredConditions[conditionResult];
        if (testIds && Object.keys(testIds).length > 0) {
          Object.keys(testIds).forEach((testId) => {
            if (!testcaseCoveredDecisions[testId]) {
              testcaseCoveredDecisions[testId] = {};
            }
            if (testcaseCoveredDecisions[testId][result] !== undefined) {
              return;
            }
            testcaseCoveredDecisions[testId][result] = true;
            testsCovered[testId] = testsCovered[testId] || ({ ...testCoveredDefault });
            testsCovered[testId].decisionCovered += 1;
          });
        }
      });
    }
  });

  // 条件覆盖
  Object.keys(decisions).forEach((decisionId) => {
    const { conditions, coveredConditions } = decisions[decisionId];
    if (!conditions) {
      return;
    }
    const conditionCount = Object.keys(conditions).length;
    conditionTotal += conditionCount * 2;
    if (coveredConditions) {
      const coveredConditionsCombine = {};
      const testcaseCoveredConditions = {};
      Object.keys(coveredConditions).forEach((conditionResult) => {
        for (let i = 0; i < conditionCount; i += 1) {
          if (conditionResult[i] === 'x') {
            // eslint-disable-next-line no-continue
            continue;
          }
          // key = (conditionId)|(0 or 1)
          const key = `${i}|${conditionResult[i]}`;
          if (!coveredConditionsCombine[key]) {
            coveredConditionsCombine[key] = true;
            conditionCovered += 1;
          }
          const { testIds } = coveredConditions[conditionResult];
          if (testIds && Object.keys(testIds).length > 0) {
            Object.keys(testIds).forEach((testId) => {
              if (!testcaseCoveredConditions[testId]) {
                testcaseCoveredConditions[testId] = {};
              }
              if (testcaseCoveredConditions[testId][key]) {
                return;
              }
              testcaseCoveredConditions[testId][key] = true;
              testsCovered[testId] = testsCovered[testId] || ({ ...testCoveredDefault });
              testsCovered[testId].conditionCovered += 1;
            });
          }
        }
      });
    }
  });

  // MC/DC覆盖
  if (mcdcKind === tests.mcdcKind.condition) {
    Object.keys(decisions).forEach((decisionId) => {
      const { conditions, coveredConditions } = decisions[decisionId];
      const conditionCount = Object.keys(conditions).length;
      if (conditionCount < 2) {
        return;
      }
      mcdcTotal += conditionCount;
      if (coveredConditions && Object.keys(coveredConditions).length > 0) {
        const result = calConditionMcdcCovered(coveredConditions);
        Object.keys(result).forEach((conditionId) => {
          mcdcCovered += 1;
          result[conditionId].forEach((coveredInfo) => {
            const [first, second] = coveredInfo.testIds;
            _.intersection(first, second).forEach((testId) => {
              testsCovered[testId] = testsCovered[testId] || ({ ...testCoveredDefault });
              testsCovered[testId].mcdcCovered += 1;
            });
          });
        });
      }
    });
  } else {
    Object.keys(decisions).forEach((decisionId) => {
      const { cases } = decisions[decisionId];
      const caseIds = Object.keys(cases);
      if (caseIds.length <= 2) {
        return;
      }
      caseIds.forEach((caseId) => {
        mcdcTotal += 1;
        const { testIds } = cases[caseId];
        if (testIds && Object.keys(testIds).length > 0) {
          mcdcCovered += 1;
          Object.keys(testIds).forEach((testId) => {
            testsCovered[testId] = testsCovered[testId] || ({ ...testCoveredDefault });
            testsCovered[testId].mcdcCovered += 1;
          });
        }
      });
    });
  }
  return {
    statementCovered,
    statementTotal,
    branchCovered,
    branchTotal,
    conditionCovered,
    conditionTotal,
    decisionCovered,
    decisionTotal,
    mcdcCovered,
    mcdcTotal,
    tests: testsCovered,
  };
}
function getInvokeCoverage({ invokes, testcaseIds }) {
  let invokedFuncCovered = 0;
  let invokeCovered = 0;
  if (invokes && Object.keys(invokes).length > 0) {
    Object.values(invokes).forEach((invoke) => {
      if (!invoke.testIds || Object.keys(invoke.testIds).length === 0) {
        return;
      }
      const invokeIds = Object.keys(invoke.testIds);
      if (testcaseIds && (testcaseIds.filter((testcaseId) => invokeIds.includes(testcaseId))).length === 0) {
        return;
      }
      invokedFuncCovered += 1;
      if (!invoke.callexpr || Object.keys(invoke.callexpr).length === 0) {
        return;
      }
      Object.values(invoke.callexpr).forEach((value) => {
        if (!value.testIds || Object.keys(value.testIds).length === 0) {
          return;
        }
        const valueIds = Object.keys(value.testIds);
        if (testcaseIds && (testcaseIds.filter((testcaseId) => valueIds.includes(testcaseId))).length === 0) {
          return;
        }
        invokeCovered += 1;
      });
    });
  }
  return { invokedFuncCovered, invokeCovered };
}

/**
 * 删除某个函数的执行结果
 * @param {String} functionId 函数id
 * @return {Promise}
 */
function removeResultOfExecution(functionId) {
  functionId = mongoose.Types.ObjectId(functionId);
  return new Promise((resolve, reject) => {
    let func;
    const coverageFields = [
      'statementCovered',
      'branchCovered',
      'conditionCovered',
      'decisionCovered',
      'mcdcCovered',
      'invokeCovered',
      'invokedFuncCovered',
    ];
    const statusFields = [
      'failedCount',
      'succeedCount',
    ];
    const updateDoc = { taskId: null };
    statusFields.concat(coverageFields).forEach((key) => {
      updateDoc[key] = 0;
    });
    Func.findOneAndUpdate({ _id: functionId }, updateDoc)
      .then((res) => {
        func = res;
        const incDoc = {};
        statusFields.concat(coverageFields).forEach((key) => {
          incDoc[key] = -func[key];
        });
        if (res.collectCoverage === false) {
          coverageFields.forEach((key) => {
            delete incDoc[key];
          });
        }
        const promises = [
          common.increaseFunction(functionId, { $inc: incDoc }),
          tests.removeResultOfExecutionInFunction(func),
        ];
        return Promise.all(promises);
      })
      .then((res) => {
        const data = {
          functionId,
          fileId: func.fileId,
          versionId: func.versionId,
          creatorId: func.creatorId,
          collectCoverage: func.collectCoverage,
          failed: null,
          branchCoverage: null,
          conditionCoverage: null,
          decisionCoverage: null,
          statementCoverage: null,
          mcdcCoverage: null,
          invokeCoverage: null,
          invokedFuncCoverage: null,
          testedTestsCount: 0,
        };
        ['branch', 'condition', 'decision', 'statement', 'mcdc', 'invoke', 'invokedFunc'].forEach((key) => {
          if (func[`${key}Total`]) {
            data[`${key}Coverage`] = 0;
          }
        });
        publisher.publishFunctionMessage('update', data);
        resolve(res);
      })
      .catch(reject);
  });
}

/**
 * 删除某个项目版本下文件的执行结果
 * @param {String} versionId 项目版本id
 * @return {Promise}
 */
async function removeResultOfExecutionInVersion(versionId) {
  versionId = mongoose.Types.ObjectId(versionId);
  const doc = {
    failedCount: 0,
    succeedCount: 0,
    statementCovered: 0,
    branchCovered: 0,
    conditionCovered: 0,
    decisionCovered: 0,
    mcdcCovered: 0,
    invokeCovered: 0,
    invokedFuncCovered: 0,
    taskId: null,
  };
  const promises = [
    Func.updateMany({ versionId }, { $set: doc }),
    tests.removeResultOfExecutionInVersion(versionId),
  ];
  await Promise.all(promises);
  const functionIds = new Set();
  (await Promise.all([
    BlackBoxCovered.find({ versionId }, ['functionId']).lean(),
    ManualCovered.find({ versionId }, ['functionId']).lean(),
  ])).forEach((covered) => {
    covered.forEach((item) => {
      functionIds.add(item.functionId.toString());
    });
  });
  if (functionIds.size > 0) {
    // eslint-disable-next-line no-use-before-define
    await utility.arrayChunkOperation([...functionIds], 10, (functionId) => updateFunctionCoverage(functionId));
  }
}

/**
 * 删除指针目标
 * @param {String} functionId 函数id
 * @param {String} variableName 变量名
 * @return {Promise}
 */
function removeMallocVariable(functionId, variableName) {
  const doc = { $unset: {} };
  doc.$unset[`malloc.${variableName}`] = 1;
  doc.$unset[`output.malloc.${variableName}`] = 1;
  const promises = [
    removeResultOfExecution(functionId),
    FunctionVariable.updateOne({ functionId }, doc),
    tests.removeMallocVariable(functionId, variableName),
  ];
  return Promise.all(promises);
}

/**
 * 删除桩函数指针目标
 * @param {String} functionId 函数id
 * @param {String} stubName 桩函数名
 * @param {String} pointerTargetName 指针目标名
 * @param {String} stubKey 桩函数键值
 * @return {Promise}
 */
function removeStubPointerTarget(functionId, stubName, pointerTargetName, stubKey = 'stubs') {
  const doc = { $unset: {} };
  doc.$unset[`${stubKey}.${stubName}.pointerTargets.${pointerTargetName}`] = 1;
  doc.$unset[`output.${stubKey}.${stubName}.pointerTargets.${pointerTargetName}`] = 1;
  const promises = [
    removeResultOfExecution(functionId),
    FunctionVariable.updateOne({ functionId }, doc),
    tests.removeStubPointerTarget(functionId, stubName, pointerTargetName, stubKey),
  ];
  return Promise.all(promises);
}

/**
 * 删除绝对地址
 * @param {String} functionId 函数Id
 * @param {String} fixedAddressName 绝对地址名
 * @return {Promise}
 */
function removeFixedAddress(functionId, fixedAddressName) {
  // eslint-disable-next-line no-use-before-define
  const fixedAddressDex = formatFixedAddressName(fixedAddressName);
  const doc = { $unset: {} };
  doc.$unset[`fixedAddrs.bases.${fixedAddressDex}`] = 1;
  const promises = [
    removeResultOfExecution(functionId),
    FunctionVariable.updateOne({ functionId }, doc),
    tests.removeFixedAddress(functionId, fixedAddressName, fixedAddressDex),
    removeOutputFixedAddress(functionId, fixedAddressDex),
  ];
  return Promise.all(promises);
}

/**
 * 取消设置全局变量检查项
 * @param {String} functionId 函数id
 * @param {String} variableName 变量名
 * @return {Promise}
 */
function removeOutputGlobalVariable(functionId, variableName) {
  const doc = { $unset: {} };
  doc.$unset[`output.global.${variableName}`] = 1;
  const promises = [
    FunctionVariable.updateOne({ functionId }, doc),
    tests.removeOutputGlobalVariable(functionId, variableName),
  ];
  return Promise.all(promises);
}
/**
 * 取消设置静态变量检查项
 * @param {String} functionId 函数id
 * @param {String} index 变量下标
 * @return {Promise}
 */
function removeOutputStaticsVariable(functionId, index) {
  const doc = { $unset: {} };
  const promises = [];
  const newIndex = handleIntegrationVariables.handleStaticsOutputFunctionId(functionId, index);
  // 兼容老数据的修改
  doc.$unset[`output.statics.${newIndex}`] = 1;
  // 入口函数输出检查项不拼接函数id
  doc.$unset[`output.statics.${index}`] = 1;
  promises.push(FunctionVariable.updateOne({ functionId }, doc));
  promises.push(tests.removeOutputStaticsVariable(functionId, index));
  return Promise.all(promises);
}

function removeOutputIntegratedFunc(functionId, mangledId) {
  const promises = [];
  const doc = { $unset: {} };
  doc.$unset[`output.${integratedFuncKey}.${mangledId}`] = 1;
  promises.push(FunctionVariable.updateOne({ functionId: mongoose.Types.ObjectId(functionId) }, doc));
  promises.push(tests.removeOutputIntegratedFunc(functionId, mangledId));
  return Promise.all(promises);
}

/**
 * 取消设置指针目标检查项
 * @param {String} functionId 函数id
 * @param {String} variableName 变量名
 * @return {Promise}
 */
function removeOutputMallocVariable(functionId, variableName) {
  const doc = { $unset: {} };
  doc.$unset[`output.malloc.${variableName}`] = 1;
  const promises = [
    FunctionVariable.updateOne({ functionId }, doc),
    tests.removeOutputMallocVariable(functionId, variableName),
  ];
  return Promise.all(promises);
}

/**
 * 取消设置成员函数所属类检查项
 * @param {String} functionId 函数id
 * @return {Promise}
 */
function removeOutputObjectVariable(functionId) {
  const doc = { $unset: {} };
  doc.$unset['output.object'] = 1;
  const promises = [
    FunctionVariable.updateOne({ functionId }, doc),
    tests.removeOutputObjectVariable(functionId),
  ];
  return Promise.all(promises);
}

/**
 * Functions表中：若函数已存在，更新之；若函数不存在，添加之
 * @param {String} functionUniqueKey 唯一健
 * @param {Object} functionData 函数需要更新的数据
 * @param {String} fileId 文件id
 * @param {String} projectVersionId 项目版本id
 * @param {String} projectId 项目id
 * @param {String} creatorId 创建者id
 * @return {Promise<any>}
 */
function replaceFunction(
  functionUniqueKey, functionData, fileId, projectVersionId, projectId, creatorId
) {
  return new Promise((resolve, reject) => {
    const key = getFunctionKey(fileId, functionUniqueKey);
    const doc = {
      ...functionData,
      creatorId: mongoose.Types.ObjectId(creatorId),
      projectId: mongoose.Types.ObjectId(projectId),
      versionId: mongoose.Types.ObjectId(projectVersionId),
      fileId: mongoose.Types.ObjectId(fileId),
      key,
      compatibleKey,
    };
    const func = new Func(doc);
    let exist = false;
    func.save()
      .then((res) => Promise.resolve(res), (err) => {
        if (err.code === 11000) {
          exist = true;
          const filter = { key };
          if (!functionData.sourceParsedFile) {
            functionData.$unset = { sourceParsedFile: 1 };
          } else {
            filter['sourceParsedFile.0'] = { $exists: 1 };
            functionData.$addToSet = { sourceParsedFile: functionData.sourceParsedFile };
            delete functionData.sourceParsedFile;
          }
          // 已经存在的函数保留原先的收集状态
          delete functionData.collectCoverage;
          return Func.updateOne(filter, functionData);
        }
        return Promise.reject(err);
      })
      .then((res) => {
        if (exist) {
          return Func.findOne({ key });
        }
        return Promise.resolve(res);
      })
      .then((func) => {
        delete doc.key;
        if (exist) {
          delete doc.functionName;
        }
        doc.functionId = func._id;
        if (!exist) {
          doc.funcsCount = 0;
          doc.testsCount = 0;
          doc.testedFuncsCount = 0;
          doc.testedTestsCount = 0;
        }
        publisher.publishFunctionMessage(exist ? 'update' : 'add', doc);
        resolve(func);
      })
      .catch(reject);
  });
}

/**
 * 清除函数覆盖率
 * @param {String} functionId 函数id
 * @param {Boolean} testRun 测试用例是否执行完成
 * @return {Promise}
 */
async function clearFunctionCovered(functionId, testRun = false) {
  functionId = mongoose.Types.ObjectId(functionId);
  const func = await Func.findOneAndUpdate(
    {
      _id: functionId,
    },
    {
      taskId: null,
      statementCovered: 0,
      branchCovered: 0,
      conditionCovered: 0,
      decisionCovered: 0,
      mcdcCovered: 0,
      invokeCovered: 0,
      invokedFuncCovered: 0,
    }
  );
  const promise = [];
  if (func.collectCoverage === true) {
    const incDoc = {
      $inc: {
        statementCovered: -func.statementCovered,
        branchCovered: -func.branchCovered,
        conditionCovered: -func.conditionCovered,
        decisionCovered: -func.decisionCovered,
        mcdcCovered: -func.mcdcCovered,
        invokeCovered: -func.invokeCovered,
        invokedFuncCovered: -func.invokedFuncCovered,
      },
    };
    promise.push(common.increaseFunction(functionId, incDoc));
  }
  promise.push(tests.removeCFGTestIds(functionId));
  // 清除函数边的覆盖
  await invokeRelations.invokeEdgeUnCoveredByFunctionId(functionId);
  promise.push(tests.clearTestcaseCoveredByFunc(functionId));
  await Promise.all(promise);
  const data = {
    functionId,
    fileId: func.fileId,
    versionId: func.versionId,
    creatorId: func.creatorId,
    collectCoverage: func.collectCoverage,
    testRun,
  };
  ['branch', 'condition', 'decision', 'statement', 'mcdc', 'invoke', 'invokedFunc'].forEach((key) => {
    if (func[`${key}Total`]) {
      data[`${key}Coverage`] = 0;
    }
  });
  publisher.publishFunctionMessage('update', data);
}

/**
 * 更新函数覆盖率数据
 * @param {String} functionId 函数id
 * @param {Boolean} testRun 测试用例是否执行完成
 * @return {Promise}
 */
async function updateFunctionCoverage(functionId) {
  functionId = mongoose.Types.ObjectId(functionId);
  const cfg = await getFunctionCFG(functionId);
  if (!cfg) {
    return;
  }
  const version = await projectVersions.getVersion(cfg.versionId);
  if (!version) {
    return;
  }
  const coverage = getCoverage(cfg, version.mcdcKind);
  const {
    statementCovered,
    statementTotal,
    branchCovered,
    branchTotal,
    conditionCovered,
    conditionTotal,
    decisionCovered,
    decisionTotal,
    mcdcCovered,
    mcdcTotal,
    tests,
  } = coverage;
  const originalFunc = await Func.findOneAndUpdate(
    { _id: functionId },
    {
      $set: {
        statementCovered,
        statementTotal,
        branchCovered,
        branchTotal,
        conditionCovered,
        conditionTotal,
        decisionCovered,
        decisionTotal,
        mcdcCovered,
        mcdcTotal,
      },
    }
  );
  const ps = [];
  if (Object.keys(tests).length) {
    [TestCase, TestCaseGroup].forEach((testResourceIns) => {
      ps.push(testResourceIns.bulkWrite(
        Object.keys(tests).map((testId) => ({
          updateOne: {
            filter: { _id: mongoose.Types.ObjectId(testId) },
            update: { $set: tests[testId] },
          },
        }))
      ));
    });
  }
  await Promise.all(ps);
  if (originalFunc.collectCoverage === false) {
    return;
  }
  // eslint-disable-next-line no-use-before-define
  const { testsCount, testedTestsCount } = await getDynamicStatistics(functionId);
  const data = {
    creatorId: originalFunc.creatorId,
    versionId: originalFunc.versionId,
    fileId: originalFunc.fileId,
    collectCoverage: originalFunc.collectCoverage,
    functionId,
    testsCount,
    testedTestsCount,
    cfgUpdate: true,
  };
  if (coverage.statementTotal) {
    data.statementCoverage = coverage.statementCovered / coverage.statementTotal;
  }
  if (coverage.branchTotal) {
    data.branchCoverage = coverage.branchCovered / coverage.branchTotal;
  }
  if (coverage.conditionTotal) {
    data.conditionCoverage = coverage.conditionCovered / coverage.conditionTotal;
  }
  if (coverage.decisionTotal) {
    data.decisionCoverage = coverage.decisionCovered / coverage.decisionTotal;
  }
  if (coverage.mcdcTotal) {
    data.mcdcCoverage = coverage.mcdcCovered / coverage.mcdcTotal;
  }
  publisher.publishFunctionMessage('update', data);
  await common.increaseFunction(functionId, {
    $inc: {
      statementCovered: statementCovered - originalFunc.statementCovered || 0,
      statementTotal: statementTotal - originalFunc.statementTotal || 0,
      branchCovered: branchCovered - originalFunc.branchCovered || 0,
      branchTotal: branchTotal - originalFunc.branchTotal || 0,
      conditionCovered: conditionCovered - originalFunc.conditionCovered || 0,
      conditionTotal: conditionTotal - originalFunc.conditionTotal || 0,
      decisionCovered: decisionCovered - originalFunc.decisionCovered || 0,
      decisionTotal: decisionTotal - originalFunc.decisionTotal || 0,
      mcdcCovered: mcdcCovered - originalFunc.mcdcCovered || 0,
      mcdcTotal: mcdcTotal - originalFunc.mcdcTotal || 0,
    },
  });
}

async function updateFunctionCoverageCollection(func, selected) {
  const fields = [
    'statementCovered',
    'statementTotal',
    'branchCovered',
    'branchTotal',
    'conditionCovered',
    'conditionTotal',
    'decisionCovered',
    'decisionTotal',
    'mcdcCovered',
    'mcdcTotal',
    'invokedFuncTotal',
    'invokedFuncCovered',
    'invokeTotal',
    'invokeCovered',
  ];
  const incDoc = {};
  fields.forEach((field) => {
    incDoc[field] = (func[field] || 0) * (selected ? 1 : -1);
  });
  const data = {
    creatorId: func.creatorId,
    versionId: func.versionId,
    fileId: func.fileId,
    functionId: func._id,
    collectCoverage: selected,
  };
  if (func.statementTotal) {
    data.statementCoverage = func.statementCovered / func.statementTotal;
  }
  if (func.branchTotal) {
    data.branchCoverage = func.branchCovered / func.branchTotal;
  }
  if (func.conditionTotal) {
    data.conditionCoverage = func.conditionCovered / func.conditionTotal;
  }
  if (func.decisionTotal) {
    data.decisionCoverage = func.decisionCovered / func.decisionTotal;
  }
  if (func.mcdcTotal) {
    data.mcdcCoverage = func.mcdcCovered / func.mcdcTotal;
  }
  if (func.invokedFuncTotal) {
    data.invokedFuncCoverage = func.invokedFuncCovered / func.invokedFuncTotal;
  }
  if (func.invokeTotal) {
    data.invokeCoverage = func.invokeCovered / func.invokeTotal;
  }
  publisher.publishFunctionMessage('update', data);
  return common.increaseFunction(func._id, { $inc: incDoc }, {
    batch: true,
    func,
  });
}

async function onFunctionCoverageSelected(selectedArr, unselectedArr) {
  const selectedFuncs = await Func.find({ _id: { $in: selectedArr } });
  const unselectedFuncs = await Func.find({ _id: { $in: unselectedArr } });
  await Promise.all(selectedFuncs.map((func) => updateFunctionCoverageCollection(func, true)));
  await Promise.all(unselectedFuncs.map((func) => updateFunctionCoverageCollection(func, false)));
}
/**
 * 删除与手动生成用例覆盖情况重复的自动生成用例
 * @param {String} functionId 函数id
 * @param {String} versionId 项目id
 * @return {Promise}
 */
function deleteFunctionDuplicateTestcases(functionId, versionId) {
  return new Promise((resolve, reject) => {
    functionId = mongoose.Types.ObjectId(functionId);
    let deletedCases = [];
    // build Map: hash -> testcaseIds
    const hashToCaseIdsMap = new Map();
    const hashToDeletedCaseIdsMap = new Map();
    let coveragesReg = '^(';

    ProjectVersion.findOne({ _id: mongoose.Types.ObjectId(versionId) })
      .then((res) => {
        const { coverages } = res;

        // eslint-disable-next-line no-bitwise
        if (coverages & tests.coverageKind.statement) {
          coveragesReg = coveragesReg.concat('nodes|edges');
        }
        // eslint-disable-next-line no-bitwise
        if (coverages & tests.coverageKind.branch) {
          coveragesReg = coveragesReg.concat('|branches');
        }
        // eslint-disable-next-line no-bitwise
        if ((coverages & tests.coverageKind.mcdc) || (coverages & tests.coverageKind.condition) || (coverages & tests.coverageKind.decision)) {
          coveragesReg = coveragesReg.concat('|decisions');
        }
        coveragesReg = coveragesReg.concat(')');
        coveragesReg = new RegExp(coveragesReg);

        return getFunctionCFG(functionId);
      })
      .then(async (cfg) => {
        if (!cfg) {
          return Promise.resolve();
        }
        let { coveredFields } = cfg;
        if (!coveredFields) {
          coveredFields = {};
          const testcases = await tests.getTestsOfFunctions(functionId);
          const testcasesCovered = await TestCaseCoveredField.find({ functionId, testcaseId: { $in: testcases.map((test) => test._id) } }).lean();
          testcasesCovered.forEach((covered) => {
            // 兼容老的数据
            if (utility.isArrayType(covered.coveredFields)) {
              coveredFields[covered.testcaseId.toString()] = covered.coveredFields;
            } else {
              coveredFields[covered.testcaseId.toString()] = Object.keys(covered.coveredFields);
            }
          });
        }
        const promises = [];
        if (coveredFields) {
          Object.keys(coveredFields).forEach((testcaseId) => {
            let coveredString = '';
            coveredFields[testcaseId].sort().forEach((coveredData) => {
              if (coveredData.search(coveragesReg) !== -1) {
                // nodes edges branches decisions
                coveredString = coveredString.concat(coveredData);
              }
            });
            coveredString = utility.md5(coveredString);
            if (!hashToCaseIdsMap.has(coveredString)) {
              hashToCaseIdsMap.set(coveredString, []);
            }
            hashToCaseIdsMap.get(coveredString).push(testcaseId);
          });

          // build hashToDeletedCaseIdsMap
          hashToCaseIdsMap.forEach((caseIdsArray, hash) => {
            if (caseIdsArray.length > 1) {
              hashToDeletedCaseIdsMap.set(hash, []);
              caseIdsArray.map((caseId) => promises.push(TestCase.findOne({ _id: mongoose.Types.ObjectId(caseId) }).then((testcase) => {
                // auto generated testcases
                if (testcase.kind !== 0) {
                  hashToDeletedCaseIdsMap.get(hash).push(caseId);
                }
                return Promise.resolve();
              })));
            }
          });
        }
        return Promise.all(promises);
      })
      .then(() => {
        // check length of hashToDeletedCaseIdsMap.value, if equals to hashToCaseIdsMap, then retain one
        hashToDeletedCaseIdsMap.forEach((value, hash) => {
          if (value.length === hashToCaseIdsMap.get(hash).length) {
            value.pop();
          }
          deletedCases = deletedCases.concat(value);
        });
        if (deletedCases.length === 0) {
          return Promise.resolve();
        }
        let deleteCasesPromises = [];
        deleteCasesPromises = Promise.resolve();
        deletedCases.forEach((testId) => {
          deleteCasesPromises = deleteCasesPromises
            .then(() => TestCase.findById(mongoose.Types.ObjectId(testId))
              .then((testcase) => {
                const baseNumber = testcase.number;
                return TestCase.updateMany(
                  {
                    functionId: testcase.functionId,
                    number: { $gt: baseNumber },
                  },
                  { $inc: { number: -1 } }
                );
              })
              .then(() => tests.removeTest(testId)));
        });
        return deleteCasesPromises;
      })
      .then(() => tests.reshuffleTestNumber(functionId))
      .then(() => updateFunctionCoverage(functionId))
      .then(resolve)
      .catch(reject);
  });
}

/**
 * 把函数和对应文件标记未读
 * @param {String} functionId 函数ID
 * @param {Boolean} unread 是否为未读状态
 * @param {Object} func 函数信息
 * @return {Promise}
 */
function updateUnread(functionId, unread, func = undefined) {
  functionId = mongoose.Types.ObjectId(functionId);
  return new Promise((resolve, reject) => {
    Promise.resolve()
      .then(() => {
        if (func) {
          return Promise.resolve(func);
        }
        return Func.findOne({ _id: functionId });
      })
      .then((res) => {
        func = res;
        if (func.unread === unread) {
          return Promise.resolve([]);
        }
        const incDoc = { $inc: {} };
        incDoc.$inc.unreadCount = 0;
        if (func.unread === true) {
          incDoc.$inc.unreadCount -= 1;
        }
        if (unread === true) {
          incDoc.$inc.unreadCount += 1;
        }
        const promises = [
          Func.updateOne({ _id: functionId }, { unread }),
          common.increaseFunction(functionId, incDoc),
        ];
        return Promise.all(promises);
      })
      .then((res) => {
        if (res.length > 0) {
          const data = {
            functionId,
            fileId: func.fileId,
            versionId: func.versionId,
            creatorId: func.creatorId,
            unread,
          };
          publisher.publishFunctionMessage('update', data);
        }
        resolve();
      })
      .catch(reject);
  });
}

/**
 * 启用默认绝对地址数据
 * @param {Object} fixedAddrs 绝对地址数据
 */
function enableDefaultFixedAddress(fixedAddrs) {
  if (fixedAddrs.exprs) {
    Object.keys(fixedAddrs.exprs).forEach((expr) => {
      const newExpr = utility.toFullWidth(expr);
      if (newExpr !== expr) {
        fixedAddrs.exprs[newExpr] = fixedAddrs.exprs[expr];
        delete fixedAddrs.exprs[expr];
      }
      if (fixedAddrs.exprs[newExpr]['@value'] > 0) {
        fixedAddrs.exprs[newExpr].enabled = 1;
      }
    });
  }
  return fixedAddrs;
}

/**
 * 比较variables是否变化
 * @param {Object} oldVariables
 * @param {Object} newVariables
 * @returns {boolean}
 */
function diffVariables(oldVariables, newVariables) {
  let different = false;

  const keys = ['variables', 'output', 'stubs', 'fixedAddrs', 'malloc'];
  keys.forEach((key) => {
    if (!oldVariables[key]) {
      oldVariables[key] = {};
    } else {
      utility.removeNullItem(oldVariables[key]);
    }
    if (!newVariables[key]) {
      newVariables[key] = {};
    } else {
      utility.removeNullItem(newVariables[key]);
    }
  });
  // 剔除 老数据的用户部分
  Object.keys(oldVariables.stubs).forEach((stubName) => {
    // filter manual stubs and constructor stubs
    if (oldVariables.stubs[stubName].modifiable === true
      || [
        tests.stubTriggerKind.manual,
        tests.stubTriggerKind.constructor,
      ].indexOf(oldVariables.stubs[stubName].stubsTriggerKind) >= 0) {
      delete oldVariables.stubs[stubName];
    } else if (oldVariables.stubs[stubName].pointerTargets) {
      delete oldVariables.stubs[stubName].pointerTargets;
    } else if (oldVariables.stubs[stubName].times) {
      delete oldVariables.stubs[stubName].times;
    }
  });
  // 剔除 老数据输入中用户自定义全局变量部分
  if (oldVariables.variables && oldVariables.variables.global) {
    Object.keys(oldVariables.variables.global).forEach((globalName) => {
      if (oldVariables.variables.global[globalName].selfDefined) {
        delete oldVariables.variables.global[globalName];
      }
    });
  }
  [oldVariables.fixedAddrs, newVariables.fixedAddrs].forEach((fixedAddress) => {
    if (!utility.isObjectType(fixedAddress)) {
      return;
    }
    if (!fixedAddress.exprs) {
      return;
    }
    Object.keys(fixedAddress.exprs).forEach((expr) => {
      delete fixedAddress.exprs[expr].enabled;
    });
  });

  const {
    variables,
    output,
    stubs,
    fixedAddrs,
  } = newVariables;
  if (!utility.isObjectValueEqual(oldVariables.fixedAddrs.exprs, fixedAddrs.exprs, ['line', 'column'])) {
    different = true;
  }
  if (!different && !utility.isObjectValueEqual(oldVariables.variables, variables, ['line', 'column'])) {
    different = true;
  }
  if (!different && !utility.isObjectValueEqual(oldVariables.output['%'], output['%'])) {
    different = true;
  }
  if (!different && !utility.isObjectValueEqual(oldVariables.stubs, stubs)) {
    Object.keys(oldVariables.stubs).forEach((stubName) => {
      if (different) {
        return;
      }
      // check if there are removed stubs
      if (!stubs[stubName]) {
        different = true;
        return;
      }
      if (!utility.isObjectValueEqual(
        oldVariables.stubs[stubName]['%'], stubs[stubName]['%']
      )) {
        different = true;
        return;
      }
      if (!utility.isObjectValueEqual(
        oldVariables.stubs[stubName]['@attributes'],
        stubs[stubName]['@attributes']
      )) {
        different = true;
        return;
      }
      if (!utility.isObjectValueEqual(
        oldVariables.stubs[stubName].params,
        stubs[stubName].params
      )) {
        different = true;
      }
    });
  }
  // check if there are new data of stubs
  if (!different) {
    Object.keys(stubs).forEach((stubName) => {
      if (different) {
        return;
      }
      if (!oldVariables.stubs[stubName]) {
        different = true;
      }
    });
  }
  return different;
}

/**
 * 处理新函数变量数据和冻结的函数变量数据数据
 * @param {Object} data 新函数变量数据
 * @return {Promise}
 */
function updateFunctionVariables(data) {
  return new Promise((resolve, reject) => {
    const functionId = mongoose.Types.ObjectId(data.functionId);
    let different = false;
    FunctionVariable.findOne({ functionId })
      .then((res) => {
        // 集成函数检查项特殊逻辑
        let outputIntegratedFuncs;
        if (res.output && res.output.integratedFuncs) {
          outputIntegratedFuncs = res.output.integratedFuncs;
          delete res.output.integratedFuncs;
        }
        // 处理新老数据
        res = res.toJSON(); // 会去掉空object导致不等

        different = diffVariables(
          JSON.parse(utility.toFullWidth(JSON.stringify(res))),
          JSON.parse(utility.toFullWidth(JSON.stringify(data)))
        );
        const {
          variables,
          output,
          stubs,
          fixedAddrs,
          malloc,
        } = data;
        const doc = { $set: {}, $unset: { infiniteLoops: 1 } };
        if (different) { // 新老数据的非用户部分不一致，直接使用新数据
          doc.$set.fixedAddrs = enableDefaultFixedAddress(fixedAddrs);
          doc.$set.variables = variables || {};
          doc.$set.stubs = stubs || {};
          doc.$set.output = output || {};
          doc.$set.malloc = malloc || {};
          doc.$unset.integratedFuncs = 1;
        } else { // 新老数据的非用户部分一致，将output、新数据的绝对地址基地址合并进来
          if (fixedAddrs.bases) {
            res.fixedAddrs = res.fixedAddrs || {};
            res.fixedAddrs.bases = res.fixedAddrs.bases || {};
            Object.keys(fixedAddrs.bases).forEach((base) => {
              if (!res.fixedAddrs.bases[base]) {
                doc.$set[`fixedAddrs.bases.${base}`] = fixedAddrs.bases[base];
              }
            });
          }
          if (output) {
            doc.$set.output = utility.deepMerge(res.output || {}, output);
            // deepMerge have problem when merging arrays, it will have duplicate exceptions.
            // so just override the exceptions with the latest data.
            doc.$set.output.exceptions = output.exceptions;
          }
          // 保留旧的集成函数添加检查项
          if (outputIntegratedFuncs) {
            doc.$set.output.integratedFuncs = outputIntegratedFuncs;
          }
        }
        return FunctionVariable.updateOne({ functionId }, doc);
      })
      .then(() => tests.getTestsOfFunctions(functionId))
      .then((res) => {
        const promises = [];
        let testsCleared = different;
        if (different) {
          // 当函数发生变化
          res.forEach((test) => {
            if (test.kind !== tests.testKind.modified) {
              // 只删除自动生成且未修改用例
              promises.push(() => tests.removeTest(test._id));
            } else {
              // 有手动编辑用例存在, 保留
              testsCleared = false;
            }
          });
          promises.push(() => BlackBoxCovered.deleteOne({ functionId }));
          promises.push(() => ManualCovered.deleteMany({ functionId }));
        }
        const promise = utility.promiseChain(promises)
          .then(() => removeResultOfExecution(functionId));
        if (!testsCleared) {
          return promise;
        }
        return promise
          .then(() => getFunction(functionId))
          .then((func) => publisher.publishFunctionMessage('update', {
            functionId,
            versionId: func.versionId,
            fileId: func.fileId,
            creatorId: func.creatorId,
            testsCleared: true,
          }));
      })
      .then(() => {
        if (different) {
          const promises = [updateUnread(functionId, true), updateFunctionCoverage(functionId)];
          return Promise.all(promises);
        }
        return Promise.resolve();
      })
      .then(resolve)
      .catch(reject);
  });
}

/**
 * 若函数变量信息已存在，更新之；若不存在，添加之
 * @param {String} projectId 项目id
 * @param {String} projectVersionId 项目版本id
 * @param {String} fileId 文件id
 * @param {String} functionId 函数id
 * @param {Object} functionVariables 函数变量信息
 * @param {boolean} integrationTest
 * @return {Promise}
 */
function replaceFunctionVariables(
  projectId,
  projectVersionId,
  fileId,
  functionId,
  functionVariables,
  // integrationTest
) {
  const {
    variables,
    stubs,
    malloc,
    output,
    fixedAddrs,
  } = functionVariables;
  return new Promise((resolve, reject) => {
    projectId = mongoose.Types.ObjectId(projectId);
    projectVersionId = mongoose.Types.ObjectId(projectVersionId);
    fileId = mongoose.Types.ObjectId(fileId);
    functionId = mongoose.Types.ObjectId(functionId);
    if (stubs) {
      Object.keys(stubs).forEach((key) => {
        stubs[key].stubsTriggerKind = tests.stubTriggerKind.parse;
      });
    }
    const doc = {
      projectId,
      versionId: projectVersionId,
      fileId,
      functionId,
      variables,
      stubs,
      malloc,
      output,
      fixedAddrs: enableDefaultFixedAddress(fixedAddrs),
      objectName: functionVariables['@object'],
    };
    // 去除没有返回值的空结构
    if (output && output['%'] && Object.keys(output['%']).length === 0) {
      delete output['%'];
    }
    const functionVariable = new FunctionVariable(doc);
    functionVariable.save()
      .then((res) => Promise.resolve(res), (err) => {
        if (err.code === 11000) {
          return updateFunctionVariables(doc);
        }
        return Promise.reject(err);
      })
      .then(() => updateFunctionOriginalVariable(functionId, doc))
      .then(resolve)
      .catch(reject);
  });
}

/**
 * 若函数CFG信息已存在，更新之；若不存在，添加之
 * @param {String} projectId 项目id
 * @param {String} projectVersionId 项目版本id
 * @param {String} fileId 文件id
 * @param {String} functionId 函数id
 * @param {Array} blocks 块信息
 * @param {Array} branches 分支信息
 * @param {Array} decisions MC/DC信息
 * @param {Array} callexpr 调用信息
 * @param {Number} mcdcKind mcdc类型
 * @return {Promise}
 */
async function replaceFunctionCFG(
  projectId,
  projectVersionId,
  fileId,
  functionId,
  {
    blocks,
    branches,
    decisions,
    callexpr,
  },
  mcdcKind
) {
  projectId = mongoose.Types.ObjectId(projectId);
  projectVersionId = mongoose.Types.ObjectId(projectVersionId);
  fileId = mongoose.Types.ObjectId(fileId);
  functionId = mongoose.Types.ObjectId(functionId);
  const projectVersion = await projectVersions.getVersion(projectVersionId);
  const cfg = getCFG(blocks, branches, decisions, mcdcKind);
  const { cycleComplexity, essentialComplexity } = getComplexity(cfg);
  const { returnCount, exitCount } = Object.values(cfg.nodes).reduce((pre, cur) => {
    if (cur.kind === cfgNodeKind.returnStat) {
      pre.returnCount += 1;
    }
    if (cur.exitPreNode) {
      pre.exitCount += 1;
    }
    return pre;
  }, { returnCount: 0, exitCount: 0 });
  const coverage = getCoverage(cfg, projectVersion.mcdcKind);
  const {
    statementCovered,
    statementTotal,
    branchCovered,
    branchTotal,
    conditionCovered,
    conditionTotal,
    decisionCovered,
    decisionTotal,
    mcdcCovered,
    mcdcTotal,
    tests,
  } = coverage;
  const coverageInc = {
    $inc: {
      statementCovered,
      statementTotal,
      branchCovered,
      branchTotal,
      conditionCovered,
      conditionTotal,
      decisionCovered,
      decisionTotal,
      mcdcCovered,
      mcdcTotal,
    },
  };
  await FunctionCFG.deleteOne({ functionId });
  const functionCFG = new FunctionCFG({
    projectId,
    versionId: projectVersionId,
    fileId,
    functionId,
    nodes: cfg.nodes,
    edges: cfg.edges,
    branches: cfg.branches,
    decisions: cfg.decisions,
    callexpr,
  });
  const promises = [Func.findOneAndUpdate({ _id: functionId }, {
    $set: {
      ...coverageInc.$inc,
      cycleComplexity,
      essentialComplexity,
      returnCount,
      exitCount,
      goto: cfg.goto,
      loopDepthMax: cfg.loopDepthMax,
      loopCount: cfg.loopCount,
    },
  })];
  promises.push(functionCFG.save());
  if (Object.keys(tests).length) {
    promises.push(TestCase.bulkWrite(
      Object.keys(tests).map((testId) => ({
        updateOne: {
          filter: { _id: mongoose.Types.ObjectId(testId) },
          update: { $set: tests[testId] },
        },
      }))
    ));
  }
  const [res] = await Promise.all(promises);
  await codeDefects.checkDeadEnd(
    cfg,
    {
      projectId,
      versionId: projectVersionId,
      fileId,
      functionId,
    }
  );
  if (res.collectCoverage === false) {
    return Promise.resolve();
  }
  const data = {
    creatorId: res.creatorId,
    functionId: res._id,
    versionId: res.versionId,
    fileId: res.fileId,
    collectCoverage: res.collectCoverage,
    statementCoverage: null,
    branchCoverage: null,
    conditionCoverage: null,
    decisionCoverage: null,
    mcdcCoverage: null,
  };
  if (coverageInc.$inc.statementTotal) {
    // eslint-disable-next-line max-len
    data.statementCoverage = res.statementCovered / coverageInc.$inc.statementTotal;
  }
  if (coverageInc.$inc.branchTotal) {
    data.branchCoverage = res.branchCovered / coverageInc.$inc.branchTotal;
  }
  if (coverageInc.$inc.conditionTotal) {
    data.conditionCoverage = res.conditionCovered / coverageInc.$inc.conditionTotal;
  }
  if (coverageInc.$inc.decisionTotal) {
    data.decisionCoverage = res.decisionCovered / coverageInc.$inc.decisionTotal;
  }
  if (coverageInc.$inc.mcdcTotal) {
    data.mcdcCoverage = res.mcdcCovered / coverageInc.$inc.mcdcTotal;
  }
  publisher.publishFunctionMessage('update', data);
  let coverageDiff = false;
  [
    'statementTotal',
    'branchTotal',
    'conditionTotal',
    'decisionTotal',
    'mcdcTotal',
    'statementCovered',
    'branchCovered',
    'conditionCovered',
    'decisionCovered',
    'mcdcCovered',
  ].forEach((key) => {
    coverageInc.$inc[key] -= res[key];
    if (coverageInc.$inc[key]) {
      coverageDiff = true;
    }
  });
  if (coverageDiff) {
    await common.increaseFunction(functionId, coverageInc);
  }
  return Promise.resolve();
}

/**
 * 更新块覆盖情况
 * @param {String} functionId 函数id
 * @param {String} versionId 版本id
 * @param {Array} blocks 块信息
 * @return {Promise}
 */
function updateCoveredBlocks(functionId, versionId, blocks) {
  functionId = mongoose.Types.ObjectId(functionId);
  const covered = {};
  blocks.forEach((block) => {
    // 节点覆盖情况
    if (block.testcases) {
      block.testcases.forEach((testId) => {
        const field = `nodes|${block.id}`;
        if (!covered[testId]) {
          covered[testId] = {
            $set: {
              versionId: mongoose.Types.ObjectId(versionId),
            },
          };
        }
        covered[testId].$set[`coveredFields.${field}`] = block.count;
      });
    }
    // 语句覆盖情况
    if (block.statements) {
      block.statements.forEach((statement) => {
        if (statement.testcases) {
          statement.testcases.forEach((testId) => {
            const field = `nodes|${block.id}|statements|${statement.id}`;
            if (!covered[testId]) {
              covered[testId] = {
                $set: {
                  versionId: mongoose.Types.ObjectId(versionId),
                },
              };
            }
            covered[testId].$set[`coveredFields.${field}`] = statement.count;
          });
        }
      });
    }
    // 边覆盖情况
    if (block.succs) {
      block.succs.forEach((succ) => {
        if (succ.testcases) {
          succ.testcases.forEach((testId) => {
            const field = `edges|${getEdgeKey(block.id, succ.id)}`;
            if (!covered[testId]) {
              covered[testId] = {
                $set: {
                  versionId: mongoose.Types.ObjectId(versionId),
                },
              };
            }
            covered[testId].$set[`coveredFields.${field}`] = succ.count;
          });
        }
      });
    }
  });
  return TestCaseCoveredField.bulkWrite(Object.keys(covered).map((testId) => ({
    updateOne: {
      filter: { functionId, testcaseId: mongoose.Types.ObjectId(testId) },
      update: covered[testId],
      upsert: true,
    },
  })));
}

/**
 * 更新分支覆盖情况
 * @param {String} functionId 函数id
 * @param {String} versionId 版本id
 * @param {Array} branches 分支信息
 * @return {Promise}
 */
function updateCoveredBranches(functionId, versionId, branches) {
  functionId = mongoose.Types.ObjectId(functionId);
  const doc = { branches: getBranches(branches) };
  const covered = {};
  Object.keys(doc.branches).forEach((branchId) => {
    if (!doc.branches[branchId].branches) {
      return;
    }
    Object.keys(doc.branches[branchId].branches).forEach((block) => {
      const { testIds } = doc.branches[branchId].branches[block];
      if (!testIds) {
        return;
      }
      Object.keys(testIds).forEach((testId) => {
        const field = `branches|${branchId}|branches|${block}`;
        if (!covered[testId]) {
          covered[testId] = {
            $set: {
              versionId: mongoose.Types.ObjectId(versionId),
            },
          };
        }
        covered[testId].$set[`coveredFields.${field}`] = testIds[testId];
      });
    });
  });
  return TestCaseCoveredField.bulkWrite(Object.keys(covered).map((testId) => ({
    updateOne: {
      filter: { functionId, testcaseId: mongoose.Types.ObjectId(testId) },
      update: covered[testId],
      upsert: true,
    },
  })));
}

async function completeCoverageInfo(functionId, cfgContent) {
  functionId = mongoose.Types.ObjectId(functionId);
  const functionCfg = await FunctionCFG.findOne({ functionId }).lean();
  const update = {};
  // 补充decisions新增的condition信息
  if (functionCfg && functionCfg.decisions) {
    const conditionsMap = {};
    if (cfgContent.decisions) {
      cfgContent.decisions.forEach((decision) => {
        if (!decision.conditions) {
          return;
        }
        conditionsMap[decision.id] = {};
        decision.conditions.forEach((condition) => {
          conditionsMap[decision.id][condition.id] = {
            begin: condition.begin, end: condition.end, F_out: condition.F_out, T_out: condition.T_out,
          };
        });
      });
    }
    Object.keys(functionCfg.decisions).forEach((decisionId) => {
      if (functionCfg.decisions[decisionId].conditionsInfo !== undefined) {
        return;
      }
      if (!conditionsMap[decisionId]) {
        return;
      }
      if (!update.$set) {
        update.$set = {};
      }
      update.$set[`decisions.${decisionId}.conditionsInfo`] = conditionsMap[decisionId];
    });
  }
  if (!utility.isEmpty(update)) {
    await FunctionCFG.updateOne({ functionId }, update);
  }
}

/**
 * 更新条件覆盖
 * @param {String} functionId 函数id
 * @param {String} versionId 版本id
 * @param {Array} decisions MC/DC判定数组
 * @return {Promise}
 */
function updateCoveredConditions(functionId, versionId, decisions) {
  const covered = {};
  decisions.forEach((decision) => {
    // 保存测试用例条件覆盖结果
    const coveredConditions = getCoveredConditions(decision);
    Object.keys(coveredConditions).forEach((coveredCondition) => {
      Object.keys(coveredConditions[coveredCondition].testIds).forEach((testId) => {
        const field = `decisions|${decision.id}|coveredConditions|${coveredCondition}`;
        if (!covered[testId]) {
          covered[testId] = {
            $set: {
              versionId: mongoose.Types.ObjectId(versionId),
            },
          };
        }
        covered[testId].$set[`coveredFields.${field}`] = coveredConditions[coveredCondition].testIds[testId];
      });
    });
  });
  const promises = [
    TestCaseCoveredField.bulkWrite(Object.keys(covered).map((testId) => ({
      updateOne: {
        filter: { functionId: mongoose.Types.ObjectId(functionId), testcaseId: mongoose.Types.ObjectId(testId) },
        update: covered[testId],
        upsert: true,
      },
    }))),
  ];
  return Promise.all(promises);
}

/**
 * 更新指针目标
 * @param {String} functionId 函数id
 * @param {String} originName 原指针目标名
 * @param {String} variableName 更新后的目标名
 * @param {String} typeName 类型名
 * @param {String} elementTypeName 数组单元素类型名
 * @param {Number} length 长度
 * @param {String} constructor 构造函数mangleId
 * @param {Object} mallocVariable 指针目标信息
 * @param {Object} fileId 指针目标类型所在文件id
 * @return {Promise}
 */
function updateMallocVariable(
  functionId,
  originName,
  {
    variableName = undefined,
    typeName = undefined,
    elementTypeName = undefined,
    length = undefined,
    constructor = undefined,
    fileId,
  },
  mallocVariable = undefined // eslint-disable-line comma-dangle
) {
  return new Promise((resolve, reject) => {
    functionId = mongoose.Types.ObjectId(functionId);
    let functionVariable;
    const newName = variableName || originName;
    let oldIsArray;
    let newIsArray;
    const doc = { $set: {} };
    const info = {};
    getFunctionVariables(functionId)
      .then((res) => {
        functionVariable = res;
        if (!mallocVariable) {
          return getMallocVariable(functionId, originName);
        }
        return Promise.resolve(mallocVariable);
      })
      .then(async (res) => {
        mallocVariable = res;
        oldIsArray = mallocVariable['@elementType'];
        newIsArray = elementTypeName || length || (!typeName && oldIsArray);
        if (newIsArray) {
          info['@elementType'] = elementTypeName || mallocVariable['@elementType'];
          info['@length'] = length ? Number.parseInt(length.toString(), 10) : mallocVariable['@length'];
          info['@type'] = getArrayTypeName(info['@elementType'], info['@length']);
        } else {
          info['@type'] = typeName || mallocVariable['@type'];
        }
        let constructorMangleIdName;
        if (constructor && utility.isStringType(constructor)) {
          constructorMangleIdName = constructor;
          if (elementTypeName) {
            info['@init'] = { '#': { '@init': { '@ctor': { '@value': constructorMangleIdName } } } };
          } else {
            info['@init'] = { '@ctor': { '@value': constructorMangleIdName } };
          }
        }
        if (newName !== originName) {
          doc.$unset = {};
          doc.$unset[`malloc.${originName}`] = 1;
        }
        doc.$set[`malloc.${newName}`] = info;
        if (functionVariable.output
          && functionVariable.output.malloc
          && functionVariable.output.malloc[originName]) {
          if (newName !== originName) {
            doc.$unset[`output.malloc.${originName}`] = 1;
          }
          doc.$set[`output.malloc.${newName}`] = info;
        }
        if (fileId && fileId.toString() !== functionVariable.fileId.toString() && doc.$set[`malloc.${newName}`]) {
          const file = await File.findOne({ _id: fileId });
          if (file) {
            doc.$set[`malloc.${newName}`].fileId = fileId.toString();
            doc.$set[`malloc.${newName}`].filePath = file.path;
          }
        }
        const promises = [];
        promises.push(removeResultOfExecution(functionId));
        promises.push(FunctionVariable.updateOne({ functionId }, doc));
        promises.push(addStubForConstructor(functionId, info['@type'], constructorMangleIdName, fileId));
        return Promise.all(promises);
      })
      .then(() => {
        if (newIsArray) {
          if (info['@elementType'] !== mallocVariable['@elementType']) {
            return tests.removeMallocVariableValue(functionId, originName);
          }
          if (info['@length'] < mallocVariable['@length']) {
            return tests.updateMallocVariable(functionId, originName, info);
          }
        } else if (info['@type'] !== mallocVariable['@type']) {
          return tests.removeMallocVariableValue(functionId, originName);
        }
        return Promise.resolve();
      })
      .then(() => {
        if (newName !== originName) {
          return tests.renameMallocVariable(functionId, originName, newName);
        }
        return Promise.resolve();
      })
      .then(resolve)
      .catch(reject);
  });
}

/**
 * 更新桩函数指针目标
 * @param {String} functionId 函数id
 * @param {String} stubName 原指针目标名
 * @param {String} originName 原指针目标名
 * @param {String} pointerTargetName 更新后的指针目标目标名
 * @param {String} typeName 类型名
 * @param {String} elementTypeName 数组单元素类型名
 * @param {Number} length 长度
 * @param {Object} pointerTarget 指针目标信息
 * @param {String} stubKey 桩函数键值
 * @return {Promise}
 */
function updateStubPointerTarget(
  functionId,
  stubName,
  originName,
  {
    pointerTargetName = undefined,
    typeName = undefined,
    elementTypeName = undefined,
    length = undefined,
  },
  pointerTarget = undefined,
  stubKey = 'stubs',
) {
  const newName = pointerTargetName || originName;
  const oldIsArray = pointerTarget['@elementType'];
  const newIsArray = elementTypeName || length || (!typeName && oldIsArray);
  let info = {};
  return new Promise((resolve, reject) => {
    functionId = mongoose.Types.ObjectId(functionId);
    Promise.resolve()
      .then(() => {
        if (!pointerTarget) {
          return getStubPointerTarget(functionId, stubName, originName, stubKey);
        }
        return Promise.resolve(pointerTarget);
      })
      .then((pointerTarget) => {
        info = {
          '@type': typeName || pointerTarget['@type'],
          '@elementType': elementTypeName || pointerTarget['@elementType'],
          '@length': length ? Number.parseInt(length.toString(), 10) : parseInt(pointerTarget['@length'], 10),
        };
        if (!newIsArray) {
          delete info['@elementType'];
          delete info['@length'];
        } else {
          info['@type'] = getArrayTypeName(info['@elementType'], info['@length']);
        }
        const promises = [removeResultOfExecution(functionId)];
        const doc = { $set: {}, $unset: {} };
        if (newName !== originName) {
          doc.$unset[`${stubKey}.${stubName}.pointerTargets.${originName}`] = 1;
        }
        doc.$set[`${stubKey}.${stubName}.pointerTargets.${newName}`] = info;
        promises.push(FunctionVariable.updateOne({ functionId }, doc));
        return Promise.all(promises);
      })
      .then(() => {
        if (newIsArray) {
          if (info['@elementType'] !== pointerTarget['@elementType']) {
            return tests.removeStubPointerTargetValue(functionId, stubName, originName, stubKey);
          }
          if (info['@length'] < pointerTarget['@length']) {
            return tests.updateStubPointerTarget(functionId, stubName, originName, info['@length'], stubKey);
          }
        } else if (info['@type'] !== pointerTarget['@type']) {
          return tests.removeStubPointerTargetValue(functionId, stubName, originName, stubKey);
        }
        return Promise.resolve();
      })
      .then(() => {
        if (newName !== originName) {
          return tests.renameStubPointerTarget(functionId, stubName, originName, newName, stubKey);
        }
        return Promise.resolve();
      })
      .then(resolve)
      .catch(reject);
  });
}

/**
 * 编辑绝对地址
 * @param {String} functionId 函数Id
 * @param {String} originName 绝对地址原名
 * @param {String} newName 绝对地址新名
 * @param {Object} pointerTargets 指针目标
 * @param {String} length 字节长度
 * @param {String} testCaseId 当前选中的测试用例id
 * @param {String} fileId 文件id
 * @param {Number} assignmentKind 绝对地址目标类型
 * @return {Promise}
 */

async function updateFixedAddress(
  functionId,
  originName,
  newName,
  pointerTargets,
  length,
  testCaseId,
  assignmentKind = fixedAddrBasesAssignmentKind.baseOffset,
) {
  const newPointerTargetsInfo = []; // 存储修改长度时，需要删除的 @value 位置
  const outputFixedAddress = await getOutputFixedAddress(functionId, originName); // 输出项中的绝对地址
  const fixedAddress = await getFixedAddress(functionId, originName);
  const func = await Func.findById(functionId);
  const testCase = await TestCase.findById(testCaseId);
  let testCaseFixedAddressOffsets;
  let testCaseOutputFixedAddressOffsets;
  const testCaseFixedAddressNewOffsets = {};
  const testCaseOutputFixedAddressNewOffsets = {};
  if (testCase
    && testCase.data
    && testCase.data.fixedAddrs
    && testCase.data.fixedAddrs.bases
    && testCase.data.fixedAddrs.bases[originName]
    && testCase.data.fixedAddrs.bases[originName].pointerTargets
    && assignmentKind === fixedAddrBasesAssignmentKind.baseOffset) {
    // 挑选出测试用例里生成的偏移量
    testCaseFixedAddressOffsets = testCase.data.fixedAddrs.bases[originName].pointerTargets;
    Object.keys(testCaseFixedAddressOffsets).forEach((key) => {
      // 偏移量类型来源variable保持测试用例中的偏移量值不变
      if (!testCaseFixedAddressOffsets[key]['@type'] && pointerTargets && pointerTargets[key]) {
        testCaseFixedAddressNewOffsets[key] = testCaseFixedAddressOffsets[key];
      }
      // 偏移量类型来自测试用例，防止测试用例中的偏移量回填到variables中
      if (testCaseFixedAddressOffsets[key]['@type'] && pointerTargets && pointerTargets[key]) {
        testCaseFixedAddressNewOffsets[key] = testCaseFixedAddressOffsets[key];
        // 更新测试用例中偏移量的类型
        if (testCaseFixedAddressOffsets[key]['@type'] !== pointerTargets[key]['@type']) {
          testCaseFixedAddressNewOffsets[key] = pointerTargets[key];
          delete testCaseFixedAddressNewOffsets[key]['@value'];
        }
        if (!fixedAddress.pointerTargets || !fixedAddress.pointerTargets[key]) {
          delete pointerTargets[key];
        }
        // variables中也有该偏移量的保持variable中的偏移量的类型不变
        if (fixedAddress.pointerTargets[key]) {
          pointerTargets[key] = fixedAddress.pointerTargets[key];
        }
      }
    });
    // 处理测试用例检查项中的偏移量
    if (testCase
      && testCase.data
      && testCase.data.output
      && testCase.data.output.fixedAddrs
      && testCase.data.output.fixedAddrs[originName]
      && testCase.data.output.fixedAddrs[originName].pointerTargets) {
      testCaseOutputFixedAddressOffsets = testCase.data.output.fixedAddrs[originName].pointerTargets;
      Object.keys(testCaseOutputFixedAddressOffsets).forEach((key) => {
        if (!testCaseOutputFixedAddressOffsets[key]['@type']) {
          testCaseOutputFixedAddressNewOffsets[key] = testCaseOutputFixedAddressOffsets[key];
          return;
        }
        if (!testCaseFixedAddressNewOffsets[key]) {
          return;
        }
        if (testCaseFixedAddressNewOffsets[key]['@type'] !== testCaseOutputFixedAddressOffsets[key]['@type']) {
          testCaseOutputFixedAddressNewOffsets[key] = { '@type': testCaseFixedAddressNewOffsets[key]['@type'] };
        }
      });
    }
  }
  const doc = { $set: {}, $unset: {} };
  newName = newName || originName;
  switch (assignmentKind) {
    case fixedAddrBasesAssignmentKind.baseOffset: {
      const newLength = length || fixedAddress['@length'];
      const newPointerTargets = pointerTargets || fixedAddress.pointerTargets;
      doc.$set[`fixedAddrs.bases.${newName}.pointerTargets`] = newPointerTargets;
      doc.$set[`fixedAddrs.bases.${newName}.@length`] = newLength;
      if (outputFixedAddress) {
        doc.$set[`output.fixedAddrs.${newName}.pointerTargets`] = newPointerTargets;
        doc.$set[`output.fixedAddrs.${newName}.@length`] = length;
        doc.$set[`output.fixedAddrs.${newName}.@hex`] = utility.convertDecToHex(newName);
        doc.$set[`output.fixedAddrs.${newName}.@dec`] = newName;
      }
      break;
    }
    case fixedAddrBasesAssignmentKind.baseExprs: {
      doc.$set[`fixedAddrs.bases.${newName}.pointerTargets`] = pointerTargets;
      if (outputFixedAddress) {
        doc.$unset[`output.fixedAddrs.${newName}`] = 1;
      }
      break;
    }
    default: break;
  }
  doc.$set[`fixedAddrs.bases.${newName}.@hex`] = utility.convertDecToHex(newName);
  doc.$set[`fixedAddrs.bases.${newName}.@dec`] = newName;
  doc.$set[`fixedAddrs.bases.${newName}.assignmentKind`] = assignmentKind;
  if (fixedAddress.fileId && func._id.toString() !== fixedAddress.fileId && fixedAddress.filePath) {
    doc.$set[`fixedAddrs.bases.${newName}.fileId`] = fixedAddress.fileId;
    doc.$set[`fixedAddrs.bases.${newName}.filePath`] = fixedAddress.filePath;
  }
  const testUpdateDoc = {
    $set: {
      [`data.fixedAddrs.bases.${newName}`]: { pointerTargets },
    },
    $unset: {
      [`data.output.fixedAddrs.${newName}`]: 1,
    },
  };
  // 处理绝对地址名
  if (newName && newName !== originName) {
    doc.$unset[`fixedAddrs.bases.${originName}`] = 1;
    testUpdateDoc.$unset[`data.output.fixedAddrs.${originName}`] = 1;
    testUpdateDoc.$unset[`data.fixedAddrs.bases.${originName}`] = 1;
    if (outputFixedAddress) {
      doc.$unset[`output.fixedAddrs.${originName}`] = 1;
    }
    await tests.renameFixedAddress(functionId, originName, newName);
  }
  if (testCaseId && assignmentKind === fixedAddrBasesAssignmentKind.baseOffset) {
    await TestCase.updateOne({ _id: testCaseId }, {
      $set: {
        [`data.fixedAddrs.bases.${newName}.pointerTargets`]: testCaseFixedAddressNewOffsets,
        [`data.output.fixedAddrs.${newName}.pointerTargets`]: testCaseOutputFixedAddressNewOffsets,
      },
    });
  }
  // 处理绝对地址下的偏移量
  if (fixedAddress.pointerTargets && assignmentKind === fixedAddrBasesAssignmentKind.baseOffset) {
    pointerTargets = !pointerTargets ? {} : pointerTargets;
    const offsets = [];
    Object.keys(fixedAddress.pointerTargets).forEach((key) => {
      if (!pointerTargets[key] // 对应偏移量已被删除
        // 非数组情况下，类型的修改
        || (pointerTargets[key]['@elementType'] && fixedAddress.pointerTargets[key]['@elementType']
          && pointerTargets[key]['@elementType'] !== fixedAddress.pointerTargets[key]['@elementType'])
        // 非数组改为数组
        || (pointerTargets[key]['@length'] && !fixedAddress.pointerTargets[key]['@length'])
        // 数组改为非数组
        || (!pointerTargets[key]['@length'] && pointerTargets[key]['@type'] !== fixedAddress.pointerTargets[key]['@type'])
      ) {
        offsets.push(key);
      } else if (pointerTargets[key] && pointerTargets[key]['@length'] < fixedAddress.pointerTargets[key]['@length']) {
        const newLength = parseInt(pointerTargets[key]['@length'], 10); // TODO
        // 绝对地址名， 偏移量名， 偏移量长度
        newPointerTargetsInfo.push({ fixedAddressName: newName, name: key, newLength });
      }
    });
    await tests.removeFixedAddressValues(functionId, newName, offsets, outputFixedAddress);
  }
  await removeResultOfExecution(functionId);
  await FunctionVariable.updateOne({ functionId }, doc);
  switch (assignmentKind) {
    case fixedAddrBasesAssignmentKind.baseOffset: {
      await Promise.all(newPointerTargetsInfo.map((info) => tests.removeFixedAddressPointerTargetsValues(functionId, info, outputFixedAddress)));
      break;
    }
    case fixedAddrBasesAssignmentKind.baseExprs: {
      await TestCase.updateMany({ functionId }, testUpdateDoc);
      break;
    }
    default: break;
  }
}

/**
 * 获取函数中某个桩函数的信息
 * @param {String} functionId 函数id
 * @param {String} stubName 桩函数名
 * @return {Promise}
 */
function getStub(functionId, stubName) {
  functionId = mongoose.Types.ObjectId(functionId);
  return new Promise((resolve, reject) => {
    getFunctionVariables(functionId)
      .then((variables) => {
        if (!variables || !variables.stubs || !variables.stubs[stubName]) {
          resolve();
        } else {
          resolve(variables.stubs[stubName]);
        }
      })
      .catch(reject);
  });
}

/**
 * 添加桩函数定义
 * @param {String} functionId 函数Id
 * @param {String} stubName 桩函数名
 * @param {Number} defaultKind 桩函数默认类型
 * @param {Array} paramTypeNames 参数类型名称列表
 * @param {Array} paramNames 参数名称列表
 * @param {String} returnType 返回值类型
 * @param {Number} times 调用次数
 * @param {Number} defaultTimes 默认调用次数
 * @param {Number} stubsTriggerKind 插桩触发类型
 * @return {Promise}
 */
function addStub(
  functionId,
  {
    stubName,
    defaultKind = tests.stubKind.value,
    paramTypeNames = [],
    paramNames = [],
    returnType = 'void',
    times = 0,
    defaultTimes = 0,
    stubsTriggerKind = tests.stubTriggerKind.manual,
  },
) {
  functionId = mongoose.Types.ObjectId(functionId);
  const newStub = {
    defaultKind,
    '%': { '@type': returnType },
    params: [],
    '@mangled': stubName,
    '@name': stubName,
    times,
    defaultTimes,
    modifiable: true,
    stubsTriggerKind,
  };
  paramTypeNames.forEach((typeName, index) => {
    newStub.params.push({ '@name': paramNames[index] || '', '@type': typeName });
  });
  const doc = { $set: {} };
  doc.$set[`stubs.${stubName}`] = newStub;
  const promises = [FunctionVariable.updateOne({ functionId }, doc)];
  const updateDoc = { $set: {} };
  updateDoc.$set[`data.stubs.${stubName}.kind`] = defaultKind;
  switch (defaultKind) {
    case tests.stubKind.value:
      updateDoc.$set[`data.stubs.${stubName}.times`] = defaultTimes;
      break;
    case tests.stubKind.code:
      updateDoc.$set[`data.stubs.${stubName}.body`] = '';
      break;
    default:
      break;
  }
  promises.push(TestCase.updateMany({ functionId }, updateDoc));
  return Promise.all(promises)
    .then(() => invokeRelations.createRelationsForManualStub(functionId, stubName, stubName));
}

/**
 * 删除某个桩函数定义
 * @param {String} functionId 函数id
 * @param {String} stubName 桩函数名
 * @return {Promise}
 */
function removeStub(functionId, stubName) {
  const doc = { $unset: {} };
  doc.$unset[`stubs.${stubName}`] = 1;
  const promises = [
    removeResultOfExecution(functionId),
    FunctionVariable.updateOne({ functionId }, doc),
    tests.removeStub(functionId, stubName),
    invokeRelations.removeRelation(functionId, stubName),
  ];
  return Promise.all(promises);
}

/**
 * 更新桩函数调用次数
 * @param {String} functionId 函数Id
 * @param {String} stubName 被修改的桩函数的名称
 * @param {Number} times 桩函数调用次数
 * @return {Promise}
 */
function updateStubTimes(functionId, stubName, times) {
  functionId = mongoose.Types.ObjectId(functionId);
  const doc = { $set: {} };
  doc.$set[`stubs.${stubName}.times`] = times;
  return FunctionVariable.updateOne({ functionId }, doc);
}

/**
 * 更新桩函数信息
 * @param {String} functionId 函数Id
 * @param {String} originStubName 原桩函数名
 * @param {Object} stubData 原桩函数数据
 * @param {Number} times 修改后的调用次数
 * @param {Number} defaultTimes 修改后的默认调用次数
 * @param {Array} paramNames 新的参数名称
 * @param {String} newStubName 新的桩函数名
 * @return {Promise}
 */
function updateStub(
  functionId,
  originStubName,
  stubData,
  {
    times,
    defaultTimes,
    paramNames,
    newStubName,
  },
) {
  functionId = mongoose.Types.ObjectId(functionId);
  newStubName = newStubName || originStubName;
  const doc = { $set: {}, $unset: {} };
  const info = JSON.parse(JSON.stringify(stubData));

  if (times || times === 0) {
    info.times = times;
  }
  if (defaultTimes || defaultTimes === 0) {
    info.defaultTimes = defaultTimes;
  }
  if (paramNames) {
    info.params.forEach((param, index) => {
      param['@name'] = paramNames[index] || '';
    });
  }
  return new Promise((resolve, reject) => {
    const promises = [removeResultOfExecution(functionId)];
    if (newStubName !== originStubName) {
      doc.$unset[`stubs.${originStubName}`] = 1;
      info['@mangled'] = info['@mangled'].replace(originStubName, newStubName);
      info['@name'] = info['@name'].replace(originStubName, newStubName);
      promises.push(
        invokeRelations.updateRelationsForManualStub(functionId, originStubName, newStubName)
      );
    }
    doc.$set[`stubs.${newStubName}`] = info;
    promises.push(FunctionVariable.updateOne({ functionId }, doc));
    Promise.all(promises)
      .then(() => {
        if (info.times < stubData.times) {
          return tests.updateStub(functionId, originStubName, times);
        }
        return Promise.resolve();
      })
      .then(() => {
        if (newStubName !== originStubName) {
          return tests.renameStub(functionId, originStubName, newStubName);
        }
        return Promise.resolve();
      })
      .then(resolve)
      .catch(reject);
  });
}

/**
 * 批量更新桩函数
 * @param {String} functionId 函数id
 * @param {Array} stubs 桩函数列表
 * @returns {Promise}
 */
async function updateStubs(functionId, stubs) {
  const functionVariable = await FunctionVariable.findOne({ functionId });
  const promises = [];
  const func = await Func.findOne({ _id: functionId }, ['language', 'versionId']);
  const version = await ProjectVersion.findOne({ _id: func.versionId });
  // eslint-disable-next-line no-use-before-define
  const isCFunc = isCFunction(func);
  const invokeFuncsMap = new Map();
  if (version.versionType === projectVersions.versionType.integrationTest) {
    const invokeFuncs = await invokeRelations.retrieveRelations(functionId, { initRelations: true });
    invokeFuncs.forEach((invokeFunc) => {
      // 需要排除参与集成函数的手动添加的桩函数
      if (invokeFunc.toFuncName === invokeFunc.toFuncMangledId
        && invokeFunc.fromFuncId.toString() !== functionId.toString()) {
        return;
      }
      let key = invokeFunc.toFuncName;
      if (!isCFunc) {
        key = invokeFunc.toFuncMangledId;
      }
      invokeFuncsMap.set(key, {});
    });
  }
  const doc = { $set: {} };
  Object.keys(stubs).forEach((stubName) => {
    const originStub = functionVariable.stubs && functionVariable.stubs[stubName];
    if (originStub && !originStub.modifiable) {
      if (stubs[stubName].times) {
        const key = `stubs.${stubName}.times`;
        doc.$set[key] = stubs[stubName].times;
      }
    } else if (!originStub && invokeFuncsMap.has(stubName)) {
      // 集成测试调用关系链中存在该函数只需要更新函数为打桩,无需回写到函数的variables中
      const updateDoc = {
        functionId,
        toFuncName: stubName,
      };
      if (!isCFunc) {
        updateDoc.toFuncMangledId = stubName;
        delete updateDoc.toFuncName;
      }
      promises.push(InvokeRelation.updateMany(updateDoc, { $set: { enableStub: true } }));
    } else {
      // TODO: 数据匹配
      const key = `stubs.${stubName}`;
      // stubs[stubName].modifiable = true;
      // 手动添加的桩函数name和mangled相同
      if (!originStub
        && stubs[stubName]['@mangled']
        && stubs[stubName]['@name']
        && stubs[stubName]['@mangled'] === stubs[stubName]['@name']) {
        promises.push(invokeRelations.createRelationsForManualStub(functionId, stubName, stubName));
        stubs[stubName].stubsTriggerKind = tests.stubTriggerKind.manual;
      }
      doc.$set[key] = stubs[stubName];
    }
  });
  promises.push(FunctionVariable.updateOne({ functionId }, doc));
  await Promise.all(promises);
}

/**
 * 根据自动生成测试用例更新函数变量
 * @param {String} functionId 函数ID
 * @param {Array} testCases 测试用例集合
 * @param {object} integratedFuncVariables 集成测试合并集成函数之后的variables
 * @return {Promise}
 */
async function updateFunctionVariable(functionId, testCases, integratedFuncVariables = null) {
  functionId = mongoose.Types.ObjectId(functionId);
  const promises = [];
  const variables = [];
  const newStubs = {};
  const stubPointerTargets = [];
  const mallocOutputVariables = [];
  const fixedAddrsOutputVariables = [];
  const globalOutputVariables = [];
  const staticOutputVariables = [];
  const funcVariable = (await getFunctionVariables(functionId)).toJSON();
  testCases.forEach((testCase) => {
    const {
      malloc,
      output,
      fixedAddrs,
      stubs,
    } = testCase;
    if (malloc) {
      Object.keys(malloc).forEach((variableName) => {
        const variable = malloc[variableName];
        variables.push({
          variableName,
          typeName: variable['@type'],
          elementTypeName: variable['@elementType'],
          length: variable['@length'],
          fileId: variable.fileId,
          filePath: variable.filePath,
        });
      });
    }
    /**
     * 测试用例中的绝对地址基地址，在variable中没有的需要补充回variable中
     */
    if (fixedAddrs && fixedAddrs.bases) {
      funcVariable.fixedAddrs = funcVariable.fixedAddrs || {};
      funcVariable.fixedAddrs.bases = funcVariable.fixedAddrs.bases || {};
      Object.keys(fixedAddrs.bases).forEach((fixedAddr) => {
        if (funcVariable.fixedAddrs.bases[fixedAddr]) {
          return;
        }
        const { pointerTargets, length } = fixedAddrs.bases[fixedAddr];
        promises.push(addFixedAddressBases(functionId, fixedAddr, length, pointerTargets));
      });
    }
    if (fixedAddrs && fixedAddrs.exprs && Object.keys(fixedAddrs.exprs).length > 0) {
      promises.push(addFixedAddressExprs(functionId, Object.keys(fixedAddrs.exprs)));
    }
    if (stubs) {
      Object.keys(stubs).forEach((stubName) => {
        const stub = stubs[stubName];
        if (!stub['@type']) {
          return;
        }
        let originalStubName = stubName;
        // 处理回写的函数文件
        if (handleIntegrationVariables.checkAttachFileIdOrFunctionId(stubName)) {
          originalStubName = handleIntegrationVariables.getOriginalName(stubName);
        }
        const oStubExist = funcVariable.stubs && funcVariable.stubs[originalStubName];
        const inStubExists = integratedFuncVariables && integratedFuncVariables.stubs && integratedFuncVariables.stubs[stubName];
        // 把测试用例生成的手动添加的桩函数回填到variables的stubs结构中
        if ((!integratedFuncVariables && !oStubExist)
          || (!oStubExist && !inStubExists && stub.stubsTriggerKind === tests.stubTriggerKind.manual)) {
          const obj = {
            ...stub['@type'],
            '@mangled': stub['@mangled'] || originalStubName,
            '@name': stub['@name'] || originalStubName,
            '@attributes': stub['@attributes'] || [],
            modifiable: true,
            stubsTriggerKind: stub.stubsTriggerKind,
          };
          if (stub.fileId && stub.filePath) {
            obj.fileId = stub.fileId;
            obj.filePath = stub.filePath;
          }
          newStubs[stubName] = obj;
        }
        // 处理桩函数执行次数
        if (!stub['@value']) {
          return;
        }
        // let times = 0;
        Object.keys(stub['@value']).forEach((index) => {
          const stubIndex = stub['@value'][index];
          if (stubIndex.pointerTargets !== undefined) {
            Object.keys(stubIndex.pointerTargets).forEach((variableName) => {
              const pointerTarget = stubIndex.pointerTargets[variableName];
              stubPointerTargets.push({
                stubName: oStubExist ? originalStubName : stubName,
                variableName,
                elementTypeName: pointerTarget['@elementType'],
                typeName: pointerTarget['@type'],
                length: pointerTarget['@length'],
              });
            });
          }
        });
      });
    }
    // globals output
    if (output && output.global) {
      Object.keys(output.global).forEach((variableName) => {
        globalOutputVariables.push(variableName);
      });
    }
    // statics output
    if (output && output.statics) {
      Object.keys(output.statics).forEach((variableName) => {
        staticOutputVariables.push(variableName);
      });
    }
    if (output && output.malloc) {
      Object.keys(output.malloc).forEach((variableName) => {
        const variable = output.malloc[variableName];
        mallocOutputVariables.push({
          variableName,
          elementTypeName: variable['@elementType'],
          typeName: variable['@type'],
          length: variable['@length'],
        });
      });
    }
    // addOutputFixedAddress
    if (output && output.fixedAddrs && fixedAddrs.bases) {
      Object.keys(output.fixedAddrs).forEach((variableName) => {
        fixedAddrsOutputVariables.push(variableName);
      });
    }
  });
  promises.push(new Promise((resolve, reject) => {
    updateStubs(functionId, newStubs)
      .then(() => addStubPointerTargets(functionId, stubPointerTargets))
      .then(resolve)
      .catch(reject);
  }));
  promises.push(addMallocVariables(functionId, variables));
  promises.push(addOutputMallocVariables(functionId, mallocOutputVariables));
  // 处理绝对地址输出
  [...new Set(fixedAddrsOutputVariables)].forEach((variableName) => {
    const variable = utility.getValueByKeyPath(funcVariable, `fixedAddrs.bases.${variableName}`);
    if (variable) {
      promises.push(addOutputFixedAddress(functionId, variableName, variable));
    }
  });
  // global
  [...new Set(globalOutputVariables)].forEach((variableName) => {
    const variable = utility.getValueByKeyPath(funcVariable, `variables.global.${variableName}`);
    if (variable) {
      promises.push(addOutputGlobalVariable(functionId, variableName, variable));
    }
  });
  // static
  [...new Set(staticOutputVariables)].forEach((variableName) => {
    const variable = utility.getValueByKeyPath(funcVariable, `variables.statics.${variableName}`);
    if (variable) {
      promises.push(addOutputStaticsVariable(functionId, variableName, variable));
    }
  });
  await Promise.all(promises);
}

/**
 * 根据Variable更新函数变量
 * @param {String} functionId 函数ID
 * @param {Array} testCases 测试用例集合
 * @return {Promise}
 */
async function updateFunctionVariableByVariable(functionId, funcVariables, importTestCase = false) {
  functionId = mongoose.Types.ObjectId(functionId);
  let resetFunctionVariable = false;
  const {
    malloc,
    output,
    stubs,
    fixedAddrs,
    variables,
    existingTestCaseCount,
    infiniteLoops,
  } = funcVariables;
  if (importTestCase) {
    resetFunctionVariable = existingTestCaseCount === 0;
  }
  const inPromises = [];
  const outPromises = [];
  const mallocInputVariables = [];
  const newStubs = {};
  const stubPointerTargets = [];
  const mallocOutputVariables = [];
  const fixedAddrsOutputVariables = [];
  const globalOutputVariables = [];
  const globalInputVariables = [];
  const staticOutputVariables = [];
  //  如果不存在测试用例，重置function variable中的output
  if (resetFunctionVariable) {
    const doc = { $unset: {} };
    doc.$unset['output.fixedAddrs'] = 1;
    doc.$unset['output.global'] = 1;
    doc.$unset['output.statics'] = 1;
    doc.$unset['output.malloc'] = 1;
    await FunctionVariable.updateOne({ functionId }, doc);
  }
  let currentFuncVariable = (await getFunctionVariables(functionId)).toJSON();
  if (malloc) {
    Object.keys(malloc).forEach((variableName) => {
      const variable = malloc[variableName];
      mallocInputVariables.push({
        variableName,
        typeName: variable['@type'],
        elementTypeName: variable['@elementType'],
        length: variable['@length'],
      });
    });
  }
  if (fixedAddrs && fixedAddrs.bases) {
    Object.keys(fixedAddrs.bases).forEach((fixedAddr) => {
      const {
        pointerTargets, length, fileId, assignmentKind,
      } = fixedAddrs.bases[fixedAddr];
      inPromises.push(addFixedAddressBases(functionId, fixedAddr, length, pointerTargets, fileId, assignmentKind));
    });
  }
  if (fixedAddrs && fixedAddrs.exprs && Object.keys(fixedAddrs.exprs).length > 0) {
    inPromises.push(addFixedAddressExprs(functionId, Object.keys(fixedAddrs.exprs)));
  }
  if (stubs) {
    currentFuncVariable.stubs = currentFuncVariable.stubs || {};
    Object.keys(stubs).forEach((stubName) => {
      const stub = stubs[stubName];
      // 回填手动添加的桩函数
      if (stub.stubsTriggerKind === tests.stubTriggerKind.manual && !currentFuncVariable.stubs[stubName]) {
        newStubs[stubName] = stub;
        return;
      }
      // 将导入的桩函数指针目标回填给当前的桩函数指针目标
      if (currentFuncVariable.stubs[stubName]
        && stub.pointerTargets
        && !currentFuncVariable.stubs[stubName].pointerTargets) {
        Object.keys(stub.pointerTargets).forEach((variableName) => {
          const pointerTarget = stub.pointerTargets[variableName];
          stubPointerTargets.push({
            stubName,
            variableName,
            elementTypeName: pointerTarget['@elementType'],
            typeName: pointerTarget['@type'],
            length: pointerTarget['@length'],
          });
        });
      }
    });
  }
  if (infiniteLoops) {
    const updateDoc = { $set: {} };
    const insertDoc = { $push: { infiniteLoops: [] } };
    const infiniteLoopsDb = new Map();
    if (currentFuncVariable.infiniteLoops) {
      currentFuncVariable.infiniteLoops.forEach((loop, idx) => {
        infiniteLoopsDb.set(`${loop.filePath}:${loop.mangledId}:${loop.nodeId}`, idx);
      });
    }
    infiniteLoops.forEach((loop) => {
      if (!infiniteLoopsDb.has(`${loop.filePath}:${loop.mangledId}:${loop.nodeId}`)) {
        insertDoc.$push.infiniteLoops.push({
          fileId: currentFuncVariable.fileId,
          functionId,
          nodeId: loop.nodeId,
          times: loop.times,
          filePath: loop.filePath,
          functionKey: loop.functionKey,
        });
      } else {
        updateDoc.$set[`infiniteLoops.${infiniteLoopsDb.get(`${loop.filePath}:${loop.mangledId}:${loop.nodeId}`)}.times`] = loop.times;
      }
    });
    inPromises.push(FunctionVariable.updateOne({ functionId }, updateDoc));
    inPromises.push(FunctionVariable.updateOne({ functionId }, insertDoc));
  }
  if (resetFunctionVariable) {
    // globals output
    if (output && output.global) {
      Object.keys(output.global).forEach((variableName) => {
        globalOutputVariables.push(variableName);
      });
    }
    // statics output
    if (output && output.statics) {
      Object.keys(output.statics).forEach((variableName) => {
        staticOutputVariables.push(variableName);
      });
    }
    if (output && output.malloc) {
      Object.keys(output.malloc).forEach((variableName) => {
        const variable = output.malloc[variableName];
        mallocOutputVariables.push({
          variableName,
          elementTypeName: variable['@elementType'],
          typeName: variable['@type'],
          length: variable['@length'],
        });
      });
    }
    // addOutputFixedAddress
    if (output && output.fixedAddrs && fixedAddrs.bases) {
      Object.keys(output.fixedAddrs).forEach((variableName) => {
        fixedAddrsOutputVariables.push(variableName);
      });
    }
  }
  // 处理输入自定义全局变量
  if (variables && variables.global) {
    const globals = variables.global;
    Object.keys(globals).forEach((globalName) => {
      if (!globals[globalName].selfDefined) {
        return;
      }
      const obj = {
        globalName,
        typeName: globals[globalName]['@type'],
      };
      if (globals[globalName]['@elementType']) {
        obj.typeName = globals[globalName]['@elementType'];
        obj.arrayLength = globals[globalName]['@length'];
      }
      globalInputVariables.push(obj);
    });
  }
  // 处理输出检查项自定义全局变量
  if (output && output.global && !resetFunctionVariable) {
    const globals = output.global;
    Object.keys(globals).forEach((globalName) => {
      if (!globals[globalName].selfDefined) {
        return;
      }
      globalOutputVariables.push(globalName);
    });
  }
  // 处理桩函数加检查项
  if (currentFuncVariable.stubs && output.stubs) {
    Object.keys(output.stubs).forEach((stubName) => {
      if (!currentFuncVariable.stubs[stubName] && !newStubs[stubName]) {
        return;
      }
      // eslint-disable-next-line no-use-before-define
      outPromises.push(addStubParamsOutput(functionId, stubName));
    });
  }
  await updateStubs(functionId, newStubs);
  await addStubPointerTargets(functionId, stubPointerTargets);
  await addMallocVariables(functionId, mallocInputVariables);
  await addOutputMallocVariables(functionId, mallocOutputVariables);
  await Promise.all(inPromises);
  if (globalInputVariables.length > 0) {
    // eslint-disable-next-line no-use-before-define
    await updateSelfDefinedGlobals(functionId, globalInputVariables);
  }
  // 需要获取最新的variables
  currentFuncVariable = (await getFunctionVariables(functionId)).toJSON();
  // 处理绝对地址输出
  [...new Set(fixedAddrsOutputVariables)].forEach((variableName) => {
    const variable = utility.getValueByKeyPath(currentFuncVariable, `fixedAddrs.bases.${variableName}`);
    if (variable) {
      outPromises.push(addOutputFixedAddress(functionId, variableName, variable));
    }
  });
  // global
  [...new Set(globalOutputVariables)].forEach((variableName) => {
    const variable = utility.getValueByKeyPath(currentFuncVariable, `variables.global.${variableName}`);
    if (variable) {
      outPromises.push(addOutputGlobalVariable(functionId, variableName, variable));
    }
  });
  // static
  [...new Set(staticOutputVariables)].forEach((variableName) => {
    const variable = utility.getValueByKeyPath(currentFuncVariable, `variables.statics.${variableName}`);
    if (variable) {
      outPromises.push(addOutputStaticsVariable(functionId, variableName, variable));
    }
  });
  await Promise.all(outPromises);
}

/**
 * 更新函数信息
 * @param {String} functionId 函数ID
 * @param {String} description 函数描述
 * @return {Promise}
 */
async function updateFunction(functionId, {
  description = undefined,
  userDefinedFields,
}) {
  functionId = mongoose.Types.ObjectId(functionId);
  const doc = { $set: {} };
  if (description || description === '') {
    doc.$set.description = description;
  }
  if (userDefinedFields && Array.isArray(userDefinedFields)) {
    doc.$set.userDefinedFields = [];
    const udfIds = userDefinedFields.map((u) => u.key).map(mongoose.Types.ObjectId);
    const udfObjs = await UserDefinedFields.find({ _id: { $in: udfIds } });

    userDefinedFields.filter(({ value }) => value !== null) // 过滤掉null的
      .forEach(({ key, value }) => {
        if (!key || typeof value !== 'string') {
          throw new PhoenixError('invalid request', 'malformed input');
        }
        const defined = udfObjs.find((o) => o._id.equals(key));
        if (!defined) {
          throw new PhoenixError('invalid request', `user defined field ${key} not found`);
        }
        if (defined.scope !== 'function') {
          throw new PhoenixError('invalid request', `user defined field ${key} scope error: ${defined.scope}`);
        }
        doc.$set.userDefinedFields.push({
          key: defined._id,
          value,
          name: defined.name,
        });
      });
  }
  await Func.updateOne({ _id: functionId }, doc);
}

/**
 * 批量更新函数信息
 * @param {Array} functions 函数列表
 * @param {Boolean} unread 是否未读
 * @return {Promise}
 */
function updateFunctions(functions, { unread = undefined }) {
  const promises = [];
  functions.forEach((func) => {
    if (unread === undefined) {
      return;
    }
    const functionId = func._id; // eslint-disable-line no-underscore-dangle
    promises.push(updateUnread(functionId, unread, func));
  });
  return Promise.all(promises);
}

/**
 * 判断函数是否为C函数
 * @param {Object} func 函数信息
 * @return {Boolean}
 */
function isCFunction(func) {
  return func.language === 'c';
}

/**
 * 判断函数是否为构造函数
 * @param {Object} func 函数信息
 * @return {Boolean}
 */
function isConstructor(func) {
  return func.functionAttrs && func.functionAttrs.indexOf('isConstructor') !== -1;
}

/**
 * 判断函数是否为编译器自动生成函数
 * @param {Object} functionAttributes 函数定义信息
 * @return {Boolean}
 */
function isDefaulted(functionAttributes) {
  return functionAttributes && functionAttributes.indexOf('isDefaulted') !== -1;
}

/**
 * 项目总览- 函数列表
 * @param {String} versionId 版本id
 * @param {Object} param1 options参数
 * @returns {Promise}
 */
async function getOverview(versionId, {
  fileId,
  status,
  changed,
  page = 1,
  pageSize = 10,
  sortBy = 'sortFilePath',
  order = 'asc',
  resKind = [],
  tested = undefined, // 是否已测试
} = {}) {
  // init sort fields
  let coveredField;
  let totalField;
  let sortField;
  const buildSortField = [];
  switch (sortBy) {
    case 'branchCoverage':
    case 'conditionCoverage':
    case 'decisionCoverage':
    case 'statementCoverage':
    case 'mcdcCoverage':
    case 'invokeCoverage':
    case 'invokedFuncCoverage':
      coveredField = '$'.concat(sortBy.slice(0, -8)).concat('Covered');
      totalField = '$'.concat(sortBy.slice(0, -8)).concat('Total');
      sortField = sortBy;
      buildSortField.push({
        $set: { [sortField]: { $cond: { if: { $gt: [totalField, 0] }, then: { $divide: [coveredField, totalField] }, else: -1 } } },
      });
      break;
    default:
      break;
  }

  const newSortBy = {};
  if (!utility.isArrayType(resKind)) {
    resKind = [resKind];
  }
  if (utility.isArrayType(sortBy)) {
    sortBy.forEach((key) => {
      newSortBy[key] = order.toLowerCase() === 'asc' ? 1 : -1;
    });
  } else {
    newSortBy[sortBy] = order.toLowerCase() === 'asc' ? 1 : -1;
  }
  // sort by function position in file tree
  newSortBy.sortFilePath = 1;
  newSortBy.lineInFile = 1;
  status = status ? [].concat(status).map((s) => parseInt(s, 10)) : undefined;
  changed = changed ? [].concat(changed) : undefined;
  const _and = [
    {
      versionId: mongoose.Types.ObjectId(versionId),
      kind: { $in: fileElement.getCountableKinds() },
      collectCoverage: true,
      $or: [
        { insideProject: { $eq: true } },
        { insideProject: { $exists: 0 } },
      ],
    },
  ];
  if (status) {
    _and.push({
      status: { $in: status },
    });
  }
  if (fileId) {
    _and.push({
      fileId: mongoose.Types.ObjectId(fileId),
    });
  }
  if (changed) {
    changed = changed.map((item) => (item === 'unmodified' ? null : item));
    _and.push({
      changed: { $in: changed },
    });
  }
  // 是否已测
  if (typeof tested === 'boolean') {
    if (tested) {
      _and.push({
        $or: [
          { succeedCount: { $gt: 0 } },
          { failedCount: { $gt: 0 } },
        ],
      });
    } else {
      _and.push({
        succeedCount: { $in: [null, 0] },
        failedCount: { $in: [null, 0] },
      });
    }
  }
  const funcResultKind = [
    {
      $lookup: {
        from: 'test-cases',
        let: { functionId: '$_id' },
        pipeline: [
          { $match: { $expr: { $eq: ['$functionId', '$$functionId'] } } },
          { $project: { _id: 0, r: '$resultKind' } },
        ],
        as: 'testcases',
      },
    },
    {
      $set: {
        testCount: { $size: '$testcases.r' },
        tested: {
          $cond: {
            if: { $or: [{ $gt: ['$failedCount', 0] }, { $gt: ['$succeedCount', 0] }] },
            then: true,
            else: false,
          },
        },
        funcResultKind: {
          $switch: {
            branches: [
              {
                case: { $eq: [0, { $size: '$testcases.r' }] },
                then: resultKind.undefined,
              },
              {
                case: {
                  $ne: [{
                    $size: {
                      $setIntersection: [[
                        tests.resultKind.failed,
                        tests.resultKind.runtimeError,
                        tests.resultKind.timeout,
                        tests.resultKind.systemError,
                        tests.resultKind.instrumentError,
                        tests.resultKind.compileError,
                      ], '$testcases.r'],
                    },
                  }, 0],
                },
                then: resultKind.failed,
              },
              {
                case: { $ne: [{ $size: { $setIntersection: [[tests.resultKind.unknown, tests.resultKind.noResult], '$testcases.r'] } }, 0] },
                then: resultKind.undefined,
              },
            ],
            default: resultKind.passed,
          },
        },
      },
    },
    {
      $unset: ['testcases'],
    },
  ];
  if (!utility.isEmpty(resKind)) {
    funcResultKind.push({
      $match: {
        funcResultKind: { $in: resKind },
      },
    });
  }
  const ret = await Func.aggregate([
    {
      $match: {
        $and: _and,
      },
    },
    ...funcResultKind,
    {
      $group:
      {
        _id: '$funcResultKind',
        count: { $sum: 1 },
      },
    },
  ]);
  const funcResultStatistic = {
    passed: 0,
    failed: 0,
    undefined: 0,
  };
  let total = 0;
  if (!utility.isEmpty(ret)) {
    Object.keys(ret).forEach((key) => {
      funcResultStatistic[ret[key]._id] = ret[key].count;
      total += ret[key].count;
    });
  }
  const arr = await Func.aggregate([
    {
      $match: {
        $and: _and,
      },
    },
    ...funcResultKind,
    {
      $lookup: {
        from: 'files',
        localField: 'fileId',
        foreignField: '_id',
        as: 'fileName',
      },
    },
    {
      $unwind: '$fileName',
    },
    // file location in tree
    {
      $addFields: { fileName: '$fileName.fileName', filePath: '$fileName.path' },
    },
    { $set: { sortPathArray: { $split: ['$filePath', '/'] } } },
    { $set: { sortFilePath: { $reduce: { input: '$sortPathArray', initialValue: '', in: { $concat: ['$$value', { $cond: [{ $eq: ['$$value', ''] }, '', '\x00'] }, '$$this'] } } } } },
    // function location in file
    {
      $set: { lineInFile: '$location.line' },
    },
    ...buildSortField,
    {
      $sort: newSortBy,
    },
    {
      $skip: (page - 1) * pageSize,
    },
    {
      $limit: pageSize,
    },
    {
      // parents
      $lookup: {
        from: 'functions',
        localField: 'parents',
        foreignField: '_id',
        as: 'parents',
      },
    },
    {
      // 不可达代码
      $lookup: {
        from: 'code-defects',
        localField: '_id',
        foreignField: 'functionId',
        as: 'codeDefects',
      },
    },
    {
      $set: {
        deadEnds: {
          $filter: {
            input: '$codeDefects',
            as: 'item',
            cond: {
              $and: [
                { $eq: ['$$item.type', 4] },
                { $gt: [{ $size: '$$item.locations' }, 0] },
              ],
            },
          },
        },
      },
    },
    {
      $project: {
        testCount: 1,
        statementCovered: 1,
        statementTotal: 1,
        branchCovered: 1,
        branchTotal: 1,
        conditionCovered: 1,
        conditionTotal: 1,
        decisionCovered: 1,
        decisionTotal: 1,
        mcdcCovered: 1,
        mcdcTotal: 1,
        status: 1,
        functionName: 1,
        fileId: 1,
        invokedFuncTotal: 1,
        invokedFuncCovered: 1,
        invokeTotal: 1,
        invokeCovered: 1,
        fileName: 1,
        filePath: 1,
        resultKind: '$funcResultKind',
        collectCoverage: 1,
        cycleComplexity: 1,
        fanIn: 1,
        fanOut: 1,
        parents: '$parents.functionName',
        deadEnds: '$deadEnds.locations',
        changed: 1,
        location: 1,
        numberOfLines: 1,
        commentLines: 1,
      },
    },

  ]).allowDiskUse(true);
  // console.log(JSON.stringify(arr, null, 1));
  arr.forEach((element) => {
    element.namespaces = element.parents.join('::');
    element.deadEnds = _.flatten(element.deadEnds);
    element.deadEnds = element.deadEnds.filter((item) => item.begin).sort((a, b) => {
      a = a.begin;
      b = b.begin;
      if (a.line > b.line) {
        return 1;
      }
      if (a.line < b.line) {
        return -1;
      }
      return a.column > b.column ? 1 : -1;
    }).map((loc) => {
      const { begin, end } = loc;
      return end
        ? `${begin.line}:${begin.column} - ${end.line}:${end.column}`
        : `${begin.line}:${begin.column}`;
    }).join('  \n');
    element.dirName = path.basename(path.dirname(element.filePath));
    fileElement.formatCoverageInfo(element);
  });
  return [arr, total, funcResultStatistic];
}

/**
 * 项目总览-函数列表下载xlsx
 * @param {String} versionId 版本id
 * @param {String} coverages 覆盖率类型
 * @param {String} excelName 文件名
 * @param {Object} locale 国际化
 * @param {boolean} tested 是否已测试
 * @returns {Promise}
 */
async function generateOverviewXlsx(versionId, coverages, excelName, locale, tested) {
  const page = 1;
  const pageSize = 1048575;
  const version = await ProjectVersion.findById(mongoose.Types.ObjectId(versionId));
  if (!version) {
    throw new Error(`version ${versionId} not found`);
  }
  const [functionList, functionTotal] = await getOverview(versionId, { page, pageSize, tested });

  // coverage info
  const coveragesName = ['invoke', 'invokedFunc', 'statement', 'branch', 'condition', 'decision', 'mcdc'];
  const coveragesArray = [];
  [
    tests.coverageKind.invoke,
    tests.coverageKind.invokedFunc,
    tests.coverageKind.statement,
    tests.coverageKind.branch,
    tests.coverageKind.condition,
    tests.coverageKind.decision,
    tests.coverageKind.mcdc,
  ].forEach((coverageKind, index) => {
    if (coverages & coverageKind) {
      coveragesArray.push(coveragesName[index]);
    }
  });

  // excel cache path
  const outputDir = path.join(cacheUtil.getConfigPath('exportDir'), randomstring.generate(16));
  await fs.ensureDir(outputDir);
  const outPath = path.join(outputDir, excelName);
  const fileExcel = new Excel();
  fileExcel.addSheet('Sheet1');

  // build data
  const columns = [{
    header: locale.__('overview.function.functionName'),
    key: 'functionName',
    width: 16.93,
  }];
  if (version.language === 'c++') {
    columns.push({
      header: locale.__('overview.function.namespaces'),
      key: 'namespaces',
      width: 16.93,
    });
  }
  columns.push(
    {
      header: locale.__('overview.function.fileName'),
      key: 'fileName',
      width: 16.93,
    }, {
      header: locale.__('overview.function.dirName'),
      key: 'dirName',
      width: 16.93,
    }, {
      header: locale.__('overview.function.filePath'),
      key: 'filePath',
      width: 16.93,
    }, {
      header: locale.__('overview.function.testCount'),
      key: 'testCount',
      width: 16.93,
    }, {
      header: locale.__('overview.function.cycleComplexity'),
      key: 'cycleComplexity',
      width: 16.93,
    }, {
      header: locale.__('overview.function.fanIn'),
      key: 'fanIn',
      width: 16.93,
    }, {
      header: locale.__('overview.function.fanOut'),
      key: 'fanOut',
      width: 16.93,
    }
  );

  const data = [];
  functionList.forEach((func) => {
    const functionData = {
      functionName: func.functionName,
      testCount: func.testCount,
      cycleComplexity: func.cycleComplexity,
      fanIn: func.fanIn,
      fanOut: func.fanOut,
      filePath: func.filePath,
      dirName: func.dirName,
      fileName: func.fileName,
      deadEnds: func.deadEnds,
      namespaces: func.namespaces,
      changed: func.changed ? locale.__(`overview.function.${func.changed}`) : '-',
    };
    let covered;
    let total;
    let result;
    let fraction;
    coveragesArray.forEach((name) => {
      covered = name.concat('Covered');
      total = name.concat('Total');
      result = name.concat('Coverage');
      fraction = name.concat('Fraction');
      functionData[fraction] = String(func[covered]).concat('/').concat(String(func[total]));
      functionData[result] = func[total] ? String(Math.round((func[covered] / func[total]) * 10000) / 100).concat('%') : '-';
    });
    functionData.resultKind = locale.__('overview.function.'.concat(func.resultKind));
    data.push(functionData);
  });

  coveragesArray.forEach((name) => {
    columns.push({
      header: locale.__('overview.file.'.concat(name).concat('Coverage')),
      key: name.concat('Coverage'),
      width: 16.93,
    });
    columns.push({
      header: locale.__('overview.file.'.concat(name).concat('CoverageRate')),
      key: name.concat('Fraction'),
      width: 16.93,
    });
  });

  if (version.oldVersionId) {
    columns.push({
      header: locale.__('overview.function.changed'),
      key: 'changed',
      width: 16.93,
    });
  }

  columns.push({
    header: locale.__('overview.function.resultKind'),
    key: 'resultKind',
    width: 16.93,
  });

  if (version.language === 'c++') {
    columns.push({
      header: locale.__('overview.function.deadEnds'),
      key: 'deadEnds',
      width: 16.93,
    });
  }

  fileExcel.addRows(columns, data);

  // style
  for (let i = 'A'.charCodeAt(0); i <= 'Z'.charCodeAt(0); i += 1) {
    for (let j = 1; j <= functionTotal + 1; j += 1) {
      fileExcel.setCell(`${String.fromCharCode(i)}${j}`);
    }
  }

  await fileExcel.writeFile(outPath);
  return [outPath, outputDir];
}

function formatFixedAddressName(fixedAddressName) {
  if (utility.isHexadecimal(fixedAddressName)) {
    fixedAddressName = utility.convertHexToDec(fixedAddressName);
  } else {
    fixedAddressName = parseInt(fixedAddressName, 10).toString();
  }
  if (fixedAddressName.indexOf('.') !== -1) {
    fixedAddressName = utility.toFullWidth(fixedAddressName);
  }
  return fixedAddressName;
}

/**
 * 获取可手动添加绝对地址表达式列表
 * @param {Object} variables 函数变量
 * @returns {Object}
 */
function getUnusedFixedAddresses(variables) {
  if (!variables.fixedAddrs || !variables.fixedAddrs.exprs) {
    return {};
  }
  Object.keys(variables.fixedAddrs.exprs).forEach((expr) => {
    if (!variables.fixedAddrs.exprs[expr].enabled) {
      return;
    }
    delete variables.fixedAddrs.exprs[expr];
  });
  return variables.fixedAddrs.exprs;
}

/**
 * 删除手动添加的绝对地址
 * @param {Object} functionId 函id
 * @param {String} exprName 表达式
 * @returns {Object}
 */
function removeFixedAddressExpr(functionId, exprName) {
  const doc = { $unset: {} };
  doc.$unset[`fixedAddrs.exprs.${exprName}.enabled`] = 1;
  const promises = [
    removeResultOfExecution(functionId),
    FunctionVariable.updateOne({ functionId }, doc),
    tests.removeFixedAddressExpr(functionId, exprName),
  ];
  return Promise.all(promises);
}
function getFunctionStubsByVersionId(versionId) {
  return FunctionVariable.find({ versionId, stubs: { $exists: 1 } }, ['fileId', 'stubs', 'functionId']).lean();
}
/**
 *获取函数可选文件列表(deprecated) todo:已定义函数可选文件范围包含调用其的文件
 * @param {string} versionId 版本id
 * @param {string} mangledId 函数mangledId
 */
async function getFuncInvokeFiles(versionId, mangledId, {
  returnFuncState = false,
  funcVariableStubs = undefined,
} = {}) {
  let isDefine = false;
  let resFiles = [];
  let fileIds = new Set();
  const funcFiles = await Func.aggregate([{
    $match: {
      mangledId,
      versionId: mongoose.Types.ObjectId(versionId),
    },
  }, {
    $project: {
      fileId: 1,
      _id: 0,
    },
  }]);
  if (funcFiles.length > 0) {
    isDefine = true;
    fileIds = new Set(funcFiles.map((file) => file.fileId));
  }
  if (funcFiles.length === 0) {
    funcVariableStubs.forEach((funcVariable) => {
      Object.keys(funcVariable.stubs).forEach((key) => {
        if (funcVariable.stubs[key]['@mangled'] === mangledId) {
          fileIds.add(funcVariable.fileId);
        }
      });
    });
  }
  resFiles = await File.aggregate([{
    $match: {
      _id: { $in: [...fileIds] },
    },
  },
  {
    $project: {
      fileId: '$_id',
      fileName: 1,
      path: 1,
      _id: 0,
    },
  },
  ]);
  if (returnFuncState) {
    return { files: resFiles, isDefine };
  }
  return resFiles;
}
async function getFileByMangledIds(versionId, mangledIds, {
  funcVariableStubs = undefined, functionId,
} = {}) {
  const resFuncMap = new Map();
  const funcMap = new Map();
  const funcVariableMap = new Map();
  const fileMap = new Map();
  const fileIds = [];
  funcVariableStubs.forEach((funcVariable) => {
    Object.keys(funcVariable.stubs).forEach((key) => {
      if (funcVariable.stubs[key]['@mangled']) {
        const mangled = funcVariable.stubs[key]['@mangled'];
        if (!funcVariableMap.has(mangled)) {
          funcVariableMap.set(mangled, []);
        }
        funcVariableMap.get(mangled).push(funcVariable.fileId.toString());
        fileIds.push(funcVariable.fileId);
      }
    });
  });

  // 入口函数
  const allMagledIds = mangledIds.concat((await Func.findById(mongoose.Types.ObjectId(functionId)).lean()).mangledId);
  const funcFiles = await Func.aggregate([{
    $match: {
      mangledId: { $in: allMagledIds },
      versionId: mongoose.Types.ObjectId(versionId),
    },
  }, {
    $project: {
      mangledId: 1,
      fileId: 1,
      _id: 0,
    },
  }, {
    $group: {
      _id: '$mangledId',
      fileIds: {
        $push: '$fileId',
      },
    },
  },
  ]);

  funcFiles.forEach((item) => {
    fileIds.push(...item.fileIds);
    funcMap.set(item._id, item);
  });
  const files = await File.aggregate([{
    $match: {
      _id: { $in: fileIds },
    },
  },
  {
    $project: {
      fileId: '$_id',
      fileName: 1,
      path: 1,
      _id: 0,
    },
  },
  ]);
  files.forEach((file) => {
    fileMap.set(file.fileId.toString(), file);
  });

  // MAP: file -> 定义在该文件中的func
  const fileToDefinedFunctionMap = new Map();
  funcMap.forEach((value, definedFunc) => {
    value.fileIds.forEach((fileId) => {
      fileId = fileId.toString();
      if (!fileToDefinedFunctionMap.has(fileId)) {
        fileToDefinedFunctionMap.set(fileId, []);
      }
      fileToDefinedFunctionMap.get(fileId).push(definedFunc);
    });
  });

  mangledIds.forEach((mangledId) => {
    // 判断是否有定义
    const func = funcMap.get(mangledId);
    let funcDefinedFileIds = [];
    let funInvokedFileIds = [];
    let isDefine = false;
    const funcFiles = [];
    if (func && func.fileIds.length > 0) {
      // 有定义函数的定义文件
      isDefine = true;
      funcDefinedFileIds = Array.from(new Set(func.fileIds)).map((definedFileId) => definedFileId.toString());
    }
    if (funcVariableMap.has(mangledId)) {
      // 有定义、无定义函数的调用文件
      funInvokedFileIds = Array.from(new Set(funcVariableMap.get(mangledId))).map((invokedFileId) => invokedFileId.toString());
    }
    funcDefinedFileIds.forEach((fileId) => {
      if (fileMap.has(fileId)) {
        funcFiles.push({ ...fileMap.get(fileId) });
      }
    });
    funInvokedFileIds.forEach((fileId) => {
      if (fileMap.has(fileId) && funcDefinedFileIds.indexOf(fileId) < 0) {
        funcFiles.push({ ...fileMap.get(fileId) });
      }
    });
    resFuncMap.set(mangledId, {
      isDefine,
      files: funcFiles,
    });
  });
  return { resFuncMap, fileToDefinedFunctionMap };
}

async function handleDuplicateSign(functionId, funcs, fileToDefinedFunctionMap) {
  const entryFunc = await Func.findById(mongoose.Types.ObjectId(functionId)).lean();
  let duplicateDefined = false;
  // build 调用函数->(其他函数的选中的)文件中的定义该函数的文件列表
  const funcToSelectedDefinedFilesMap = new Map();
  const allFuncs = funcs.concat({
    mangledId: entryFunc.mangledId,
    fileId: entryFunc.fileId,
    isDefine: true,
  });

  allFuncs.forEach((func) => {
    if (func.fileId && fileToDefinedFunctionMap.has(func.fileId.toString())) {
      const fileId = func.fileId.toString();
      fileToDefinedFunctionMap.get(fileId).forEach((definedFunc) => {
        if (!funcToSelectedDefinedFilesMap.has(definedFunc)) {
          funcToSelectedDefinedFilesMap.set(definedFunc, []);
        }
        if (definedFunc !== func.mangledId) {
          funcToSelectedDefinedFilesMap.get(definedFunc).push(fileId);
        }
      });
    }
  });

  // duplicatedDefined judge
  allFuncs.forEach((func) => {
    if (func.isDefine) {
      if (funcToSelectedDefinedFilesMap.has(func.mangledId)) {
        let selectedFiles = funcToSelectedDefinedFilesMap.get(func.mangledId);
        // 其他函数选中文件中已产生冲突 || 与该函数选中的文件冲突
        selectedFiles = utility.arrayDeduplicate(selectedFiles);
        if (selectedFiles.length > 1 || (selectedFiles.length && !selectedFiles.includes(func.fileId.toString()))) {
          duplicateDefined = true;
        }
      }
    }
  });
  return duplicateDefined;
}

async function getFunctionByVersionId(versionId, fields) {
  const allFuncsMap = new Map();
  const allFuncs = await Func.find({ versionId, kind: { $in: fileElement.getCountableKinds() } }, fields).lean();
  allFuncs.forEach((func) => {
    allFuncsMap.set(func._id.toString(), func);
  });
  return allFuncsMap;
}
async function getInvokeFunctionsVariable(
  functionId,
  invokeFuncs,
  funcVariableStubs,
  versionId,
  fileId,
  removeInvokeFuncManualStub = false
) {
  const functionIds = [functionId];
  const fileIds = [];
  let variableMap = new Map();
  const toFuncMangledIds = [];
  if (fileId) {
    fileIds.push(mongoose.Types.ObjectId(fileId));
  }
  invokeFuncs.forEach((invokeFunc) => {
    if (invokeFunc.toFuncId !== null) {
      functionIds.push(invokeFunc.toFuncId);
    }
    functionIds.push(invokeFunc.fromFuncId);
    fileIds.push(mongoose.Types.ObjectId(invokeFunc.toFuncFileId));
    toFuncMangledIds.push(invokeFunc.toFuncMangledId);
  });
  const { resFuncMap: toFuncMangleMap, fileToDefinedFunctionMap } = await getFileByMangledIds(
    versionId,
    toFuncMangledIds,
    { funcVariableStubs, functionId }
  );
  if (functionIds.length > 0) {
    variableMap = await getFunctionVariables(functionIds, true);
  }
  // 移除参与集成函数中手动添加的桩函数
  if (removeInvokeFuncManualStub) {
    variableMap.forEach((variables, key) => {
      if (key !== functionId.toString() && variables.stubs) {
        Object.keys(variables.stubs).forEach((stubName) => {
          if (variables.stubs[stubName].stubsTriggerKind
            && variables.stubs[stubName].stubsTriggerKind === tests.stubTriggerKind.manual) {
            delete variables.stubs[stubName];
          }
        });
      }
    });
  }
  return {
    variableMap, toFuncMangleMap, fileIds, fileToDefinedFunctionMap,
  };
}
/**
 * 获取当前函数的调用函数
 * @param {Object} entryFuncVariables 入口函数variables
 * @param {Boolean} includeVariable
 * @param {Map} allFileMap
 * @param {Map} toFuncMangleMap
 * @param {Map} variableMap
 */
function formatFuncInvoke(invokeFunc,
  {
    entryFuncVariables = undefined,
    allFileMap,
    variableMap,
    toFuncMangleMap = new Map(),
    includeVariable = true,
  } = {}) {
  const newInvokeFunc = {
    functionId: invokeFunc.toFuncId || '',
    fromFuncId: invokeFunc.fromFuncId,
    functionName: invokeFunc.toFuncName,
    mangledId: invokeFunc.toFuncMangledId,
    enableStub: invokeFunc.enableStub,
    isDefine: !!invokeFunc.toFuncId,
    fileName: '',
    filePath: '',
    files: toFuncMangleMap.has(invokeFunc.toFuncMangledId) ? toFuncMangleMap.get(invokeFunc.toFuncMangledId).files : [],
    fileId: invokeFunc.toFuncFileId.toString(),
    toFuncFileUpdate: invokeFunc.toFuncFileUpdate,
    attributes: [],
    returnType: '',
  };
  let variables;
  const invokeFuncInfo = allFileMap.get(invokeFunc.toFuncFileId.toString());
  if (invokeFuncInfo) {
    newInvokeFunc.fileName = invokeFuncInfo.fileName;
    newInvokeFunc.filePath = invokeFuncInfo.path;
  }
  if (invokeFunc.toFuncId !== null) {
    variables = variableMap.get(invokeFunc.toFuncId.toString());
    if (variables && variables.output) {
      newInvokeFunc.returnType = variables.output['%'] ? variables.output['%']['@type'] : 'void';
    }
  } else {
    variables = variableMap.get(invokeFunc.fromFuncId.toString());
    if (variables && variables.stubs) {
      Object.keys(variables.stubs).forEach((stubName) => {
        // 桩函数C++使用的是mangledId,c使用的是函数名
        if ([invokeFunc.toFuncName, invokeFunc.toFuncMangledId].includes(stubName)) {
          newInvokeFunc.returnType = variables.stubs[stubName]['%'] ? variables.stubs[stubName]['%']['@type'] : 'void';
          newInvokeFunc.attributes = variables.stubs[stubName]['@attributes'];
          newInvokeFunc.nameparams = variables.stubs[stubName]['@nameparams'];
        }
      });
    }
  }
  if (entryFuncVariables) {
    let stubKey = 'stubs';
    if (!invokeFunc.enableStub) {
      stubKey = integratedFuncKey;
    }
    // 处理指针目标
    if (entryFuncVariables[stubKey]) {
      Object.keys(entryFuncVariables[stubKey]).forEach((stubName) => {
        // 桩函数C++使用的是mangledId,c使用的是函数名,包含拼接文件的情况
        const names = [invokeFunc.toFuncName, invokeFunc.toFuncMangledId];
        if (newInvokeFunc.fileId) {
          names.push(`${invokeFunc.toFuncName}@${newInvokeFunc.fileId}`);
          names.push(`${invokeFunc.toFuncMangledId}@${newInvokeFunc.fileId}`);
        }
        if ((names.includes(stubName)) && entryFuncVariables[stubKey][stubName].pointerTargets) {
          newInvokeFunc.pointerTargets = entryFuncVariables[stubKey][stubName].pointerTargets;
        }
      });
    }
  }
  if (includeVariable && variables) {
    newInvokeFunc.variables = variables;
  }
  newInvokeFunc.isCreateManual = newInvokeFunc.functionName === newInvokeFunc.mangledId;
  newInvokeFunc.insideProject = allFileMap.get(newInvokeFunc.fileId).kind === fileModels.kind.file;
  return newInvokeFunc;
}

async function invokeGraph(functionId) {
  let arr = await invokeRelations.retrieveRelations(functionId, { sort: true });
  const fromIds = new Set();
  const toIds = new Set();
  if (!arr.length) {
    // 一条边都没有的情况，手动插入一条空边给前端显示一个单节点
    arr.push({
      fromFuncId: mongoose.Types.ObjectId(functionId),
      toFuncId: null,
    });
  }
  arr = arr.map(({
    fromFuncId, fromFuncFileId, toFuncId, toFuncName, toFuncFileId,
    fromFuncMangledId, toFuncMangledId,
  }) => {
    if (fromFuncId) {
      fromIds.add(fromFuncId.toString());
    }
    if (toFuncId) {
      toIds.add(toFuncId.toString());
    }
    return {
      from: {
        functionId: fromFuncId,
        functionName: null,
        fileId: fromFuncFileId,
        location: null,
        mangledId: fromFuncMangledId,
      },
      to: {
        functionId: toFuncId,
        functionName: toFuncName,
        fileId: toFuncFileId,
        location: null,
        mangledId: toFuncMangledId,
      },
    };
  });

  const funcs = await Func.find({
    _id: { $in: [...fromIds, ...toIds].map(mongoose.Types.ObjectId) },
  });
  const variables = await FunctionVariable.find({
    functionId: { $in: [...fromIds].map(mongoose.Types.ObjectId) },
  }).lean();
  const nonStaticMap = new Map();
  return arr.map(({ from, to }) => {
    const fromFunc = funcs.find((f) => f._id.toString() === from.functionId.toString());
    const toFunc = to.functionId
      ? funcs.find((f) => f._id.toString() === to.functionId.toString())
      : null;
    if (fromFunc) {
      from.functionName = fromFunc.functionName;
      from.location = fromFunc.location;
      from.fileId = from.fileId || fromFunc.fileId;
      from.mangledId = from.mangledId || fromFunc.mangledId;
    }
    if (toFunc) {
      to.functionName = toFunc.functionName;
      to.location = toFunc.location;
    }
    if (!to.functionId) {
      // 对于为定义非static函数，全局唯一，必须统一fileId，取第一个，对于已定义函数无需处理
      const variable = variables
        .find(({ functionId }) => functionId.toString() === from.functionId.toString());
      if (variable && variable.stubs) {
        const stub = Object.values(variable.stubs).find((s) => s['@mangled'] === to.mangledId);
        if (stub && stub['@attributes'] && !stub['@attributes'].find((attr) => attr === 'isStatic')) {
          if (!nonStaticMap.get(to.mangledId)) {
            nonStaticMap.set(to.mangledId, to);
          }
          to.fileId = nonStaticMap.get(to.mangledId).fileId;
        }
      }
    }
    return { from, to };
  });
}
/**
 * 参与集成函数边去重
 * @param {Array[Object]} relationFuncs
 * @param {ObjectId:String} versionId
 * @returns
 */
async function relationFuncsDeduplicate(relationFuncs, versionId) {
  const resFuncs = [];
  const mangledIdKeys = new Set();
  const staticFunctionIds = new Set();
  const definedFunctionsMap = new Map();
  const allFuncs = await Func.find({ versionId, kind: { $in: fileElement.getCountableKinds() } }, [
    '_id',
    'mangledId',
    'functionAttrs',
    'fileId']).lean();
  allFuncs.forEach((func) => {
    definedFunctionsMap.set(func._id.toString(), func);
  });
  relationFuncs.forEach((edgeFunc) => {
    const { toFuncMangledId, toFuncId } = edgeFunc;
    const toFuncData = toFuncId ? definedFunctionsMap.get(toFuncId.toString()) : null;
    // 未定义函数
    if (!toFuncData && !mangledIdKeys.has(toFuncMangledId)) {
      mangledIdKeys.add(toFuncMangledId);
      resFuncs.push(edgeFunc);
      return;
    }
    // 已定义函数要区分静态函数的情况
    if (toFuncData) {
      if (toFuncData.functionAttrs.includes('isStatic') && !staticFunctionIds.has(toFuncId.toString())) {
        staticFunctionIds.add(toFuncId.toString());
        resFuncs.push(edgeFunc);
        return;
      }
      if (!toFuncData.functionAttrs.includes('isStatic') && !mangledIdKeys.has(toFuncMangledId)) {
        mangledIdKeys.add(toFuncMangledId);
        resFuncs.push(edgeFunc);
      }
    }
  });
  return resFuncs;
}

/**
 * 集成函数和被测函数variables合并
 * @param {Object} originalVariables 原始variable
 * @param {Object} targetVariables 需要合并的variable
 * @returns
 */
function mergeFunctionVariables(originalVariables, targetVariables) {
  const {
    variables,
  } = targetVariables;
  if (variables) {
    if (!originalVariables.variables) {
      originalVariables.variables = {};
    }
    if (variables.global) {
      originalVariables.variables = originalVariables.variables || {};
      originalVariables.variables.global = originalVariables.variables.global || {};
      Object.keys(variables.global).forEach((key) => {
        if (originalVariables.variables.global[key]) {
          return;
        }
        originalVariables.variables.global[key] = variables.global[key];
      });
    }
    if (variables.statics) {
      if (originalVariables.variables.statics) {
        Object.assign(originalVariables.variables.statics, variables.statics);
      } else {
        originalVariables.variables.statics = { ...variables.statics };
      }
    }
  }
  return originalVariables;
}

// external globals非static的合并为一个
async function mergeExternalGlobals(externalGlobals, { spliceFileId = false } = {}) {
  const fileIds = externalGlobals.map(({ fileId }) => fileId);
  const fileGlobals = await FileGlobalVariable.aggregate([
    { $match: { fileId: { $in: fileIds } } },
    {
      $lookup: {
        from: 'files', localField: 'fileId', foreignField: '_id', as: 'file',
      },
    },
    { $unwind: '$file' },
    {
      $addFields: { filePath: '$file.path' },
    },
    {
      $unset: 'file',
    },
  ]);

  return externalGlobals.reduce((map, { fileId, fullname }) => {
    const fg = fileGlobals.find((item) => item.fileId.toString() === fileId.toString());
    if (fg && fg.globals && fg.globals[fullname]) {
      const splicedStr = spliceFileId ? fg.fileId.toString() : fg.filePath;
      if (fg.globals[fullname]['@attributes'].find((attr) => attr === 'isStatic')) {
        // 静态全局变量
        const key = `${fullname}@${splicedStr}`;
        map[key] = {
          ...fg.globals[fullname],
          isExternalGlobal: true,
          fileId: fg.fileId,
          filePath: fg.filePath,
        };
      } else {
        // 非静态
        const key = fullname;
        map[key] = map[key] || {
          ...fg.globals[fullname],
          isExternalGlobal: true,
          fileId: fg.fileId,
          filePath: fg.filePath,
        };
      }
    }
    return map;
  }, {});
}

/**
 * 集成测试函数variables拼接文件
 * @param {object} variables
 */
async function functionVariableSpliceFileId(funVariables, {
  spliceFileId = false,
  isOriginal = false,
  entryFuncFileId = undefined,
  allFuncMap,
  allFileMap,
} = {}) {
  const {
    functionId,
    fileId,
    variables,
    malloc,
    output,
    fixedAddrs,
    externalGlobals,
  } = funVariables;
  const resVariables = {
    fileId, functionId, output, fixedAddrs,
  };
  if (funVariables.objectName) {
    resVariables.objectName = funVariables.objectName;
  }
  let file;
  if (allFileMap && allFileMap.has(fileId.toString())) {
    file = allFileMap.get(fileId.toString());
  } else {
    file = await File.findById(fileId);
  }
  if (variables) {
    if (variables.global) {
      variables.global = handleIntegrationVariables.handleGlobals(variables.global, {
        file,
        type: 'attachFileId',
        attachFile: entryFuncFileId !== fileId.toString(),
        isEntryFunc: isOriginal,
        allFileIdsOfVersion: allFileMap,
      });
    }
    if (variables.statics) {
      variables.statics = handleIntegrationVariables.handleStatics(variables.statics, {
        type: 'attachFuncId',
        isEntryFunc: isOriginal,
        allFuncIdsMap: allFuncMap,
        allFileIdsOfVersion: allFileMap,
      });
    }
    resVariables.variables = variables;
  }
  if (malloc) {
    resVariables.malloc = handleIntegrationVariables.handleMallocs(malloc, {
      file,
      type: 'attachFileId',
      isEntryFunc: isOriginal,
    });
  }
  if (externalGlobals && externalGlobals.length) {
    resVariables.variables = resVariables.variables || {};
    resVariables.variables.global = resVariables.variables.global || {};
    resVariables.variables.global = _.defaults(resVariables.variables.global,
      await mergeExternalGlobals(externalGlobals, { spliceFileId }));
  }
  return resVariables;
}

function filterRelationFuncStub(relationFunc, funcMap, fileMap, variableMap) {
  let stub = null;
  const {
    fileId, mangledId, enableStub, functionId, fromFuncId,
  } = relationFunc;
  const variables = variableMap.get(fromFuncId.toString());
  if (!variables) {
    logger.error(`-------relation fromFuncId:${fromFuncId.toString()} variables not exist-------`);
    return stub;
  }
  const stubs = variables.stubs || {};
  Object.keys(stubs).forEach((key) => {
    if (stubs[key]['@mangled'] === mangledId) {
      stub = stubs[key];
      stub['@key'] = key;
    }
  });
  if (!stub) {
    return stub;
  }
  stub.enableStub = enableStub;
  if (functionId) {
    stub.functionId = functionId;
    const func = funcMap.get(functionId.toString());
    if (func) {
      stub['@attributes'] = stub['@attributes'] || [];
      stub['@attributes'] = utility.arrayDeduplicate([...stub['@attributes'], ...func.functionAttrs]);
    }
  }
  const file = fileMap.get(fileId.toString());
  if (file) {
    stub.fileId = file._id.toString();
    stub.filePath = file.path;
  } else {
    stub.fileId = '';
    stub.filePath = '';
  }
  return stub;
}
function handleIntegrationFuncOutput(funcVariables, fileMap, removeVariableDoc) {
  const file = fileMap.get(funcVariables.fileId.toString());
  // 输出检查项绝对地址和输入选择文件一致
  if (funcVariables.output.fixedAddrs) {
    const outputFixedAddrs = funcVariables.output.fixedAddrs;
    Object.keys(outputFixedAddrs).forEach((key) => {
      if (funcVariables.fixedAddrs.bases[key]) {
        outputFixedAddrs[key].fileId = funcVariables.fixedAddrs.bases[key].fileId;
        outputFixedAddrs[key].filePath = funcVariables.fixedAddrs.bases[key].filePath;
      }
    });
    funcVariables.output.fixedAddrs = outputFixedAddrs;
  }
  // 处理局部静态变量输出检查项文件
  if (funcVariables.variables
    && funcVariables.variables.statics
    && funcVariables.output
    && funcVariables.output.statics
  ) {
    Object.keys(funcVariables.output.statics).forEach((staticName) => {
      if (!funcVariables.variables.statics[staticName]) {
        delete funcVariables.output.statics[staticName];
        removeVariableDoc.$unset[`output.statics.${staticName}`] = 1;
        removeVariableDoc.$unset[`data.output.statics.${staticName}`] = 1;
        return;
      }
      const { fileId, filePath } = funcVariables.variables.statics[staticName];
      funcVariables.output.statics[staticName].fileId = fileId;
      funcVariables.output.statics[staticName].filePath = filePath;
    });
  }
  // 处理全局变量输出检查项文件
  if (funcVariables.variables
    && funcVariables.variables.global
    && funcVariables.output
    && funcVariables.output.global
  ) {
    Object.keys(funcVariables.output.global).forEach((globalName) => {
      if (!funcVariables.variables.global[globalName]) {
        delete funcVariables.output.global[globalName];
        removeVariableDoc.$unset[`output.global.${globalName}`] = 1;
        removeVariableDoc.$unset[`data.output.global.${globalName}`] = 1;
        return;
      }
      const { fileId, filePath } = funcVariables.variables.global[globalName];
      funcVariables.output.global[globalName].fileId = fileId;
      funcVariables.output.global[globalName].filePath = filePath;
    });
  }

  // 处理指针目标输出检查项文件
  if (funcVariables.malloc && funcVariables.output && funcVariables.output.malloc) {
    Object.keys(funcVariables.output.malloc).forEach((mallocName) => {
      if (!funcVariables.malloc[mallocName]) {
        delete funcVariables.output.malloc[mallocName];
        removeVariableDoc.$unset[`output.malloc.${mallocName}`] = 1;
        removeVariableDoc.$unset[`data.output.malloc.${mallocName}`] = 1;
        return;
      }
      const { fileId, filePath } = funcVariables.malloc[mallocName];
      funcVariables.output.malloc[mallocName].fileId = fileId;
      funcVariables.output.malloc[mallocName].filePath = filePath;
    });
  }
  // 返回值添加文件
  if (funcVariables.output && funcVariables.output['%'] && file) {
    funcVariables.output['%'].fileId = file._id.toString();
    funcVariables.output['%'].filePath = file.path;
  }
  // 处理输出检查项中桩函数的文件
  if (funcVariables.stubs
    && funcVariables.output
    && funcVariables.output
    && funcVariables.output.stubs
  ) {
    const outputStubs = funcVariables.output.stubs;
    const inputStubs = funcVariables.stubs;
    Object.keys(outputStubs).forEach((stubName) => {
      if (inputStubs[stubName] && inputStubs[stubName].fileId && inputStubs[stubName].filePath) {
        outputStubs[stubName].fileId = inputStubs[stubName].fileId;
        outputStubs[stubName].filePath = inputStubs[stubName].filePath;
        outputStubs[stubName].mangled = inputStubs[stubName]['@mangled'];
      }
    });
  }
}
/**
 * 获取版本中的所有全局变量文件
 * @param {string|ObjectId} versionId 版本id
 * @param {array[string]} attributes 全局变量定义的类型
 * return Map<globalName,Array[Object(file)]>
 */
async function getVersionGlobalVariables(versionId, attribute) {
  const globals = new Map();
  const fileGlobals = await FileGlobalVariable.find({ versionId: mongoose.Types.ObjectId(versionId), globals: { $exists: true } }, {
    globals: 1,
    fileId: 1,
  }).lean();
  fileGlobals.forEach((fileGlobal) => {
    if (fileGlobal.globals) {
      Object.keys(fileGlobal.globals).forEach((globalName) => {
        const globalVariable = fileGlobal.globals[globalName];
        if (globalVariable['@attributes'] && globalVariable['@attributes'].includes(attribute)) {
          if (!globals.has(globalName)) {
            globals.set(globalName, []);
          }
          globals.get(globalName).push({
            fileId: fileGlobal.fileId.toString(),
            '@attributes': globalVariable['@attributes'],
            '@name': globalVariable['@name'],
            '@type': globalVariable['@type'],
          });
        }
      });
    }
  });
  return globals;
}
/**
 *获取被测函数variables
 * @param {String} functionId 被测函数id
 * @param {Boolean} spliceFileId  true拼接文件id
 * @param {Boolean} includeNonStub  stubs中是否返回未打桩的函数
 * @param {Boolean} includeSpecializationFunc  true 包含特化函数，false去除特化函数
 * @param {Map} allFileMap  所有文件
 * @param {Map} allFuncVariableStubs 版本下所有stubs
 * @param {Map} allFuncMap  所有函数
 * @param {Map} allFileGlobals  版本所有全局变量
 * @returns object
 */
async function getRelationFuncsVariables(
  functionId,
  {
    spliceFileId = true,
    includeNonStub = false,
    allFileMap = undefined,
    allFuncVariableStubs = undefined,
    allFuncMap = undefined,
    allFileGlobals,
    includeSpecializationFunc = true,
  } = {}
) {
  let funcVariables = await getFunctionVariables(functionId, true);
  if (!funcVariables) {
    throw new Error(`function variables(${functionId}) not found`);
  }
  funcVariables = funcVariables.toObject({ minimize: false });
  const { fileId, versionId, integratedFuncs } = funcVariables;
  const entryFuncFileId = fileId.toString();
  const integratedFuncMangleMap = new Map();
  const entryStubsMap = new Map();
  const entryStubs = funcVariables.stubs || {};
  const originalGlobals = funcVariables.variables ? JSON.parse(JSON.stringify(funcVariables.variables.global || {})) : {};
  Object.keys(entryStubs).forEach((key) => {
    entryStubsMap.set(key, entryStubs[key]);
  });
  if (!allFuncVariableStubs) {
    allFuncVariableStubs = await getFunctionStubsByVersionId(versionId);
  }
  if (!allFileMap) {
    allFileMap = await fileModels.getSourceFileByVersionId(versionId, null, { _id: 1, path: 1, fileName: 1 });
  }
  if (!allFuncMap) {
    allFuncMap = await getFunctionByVersionId(versionId, {
      _id: 1, fileId: 1, functionName: 1, functionAttrs: 1, kind: 1,
    });
  }
  if (!allFileGlobals) {
    allFileGlobals = await getVersionGlobalVariables(versionId, 'isDefined');
  }
  const funcKinds = [fileElement.elementKind.function];
  // 过滤特化函数
  if (includeSpecializationFunc) {
    funcKinds.push(fileElement.elementKind.specialFunction);
  }
  let retrieveRelationFuncs = await invokeRelations.retrieveRelations(functionId, { sort: true, initRelations: true });
  retrieveRelationFuncs = retrieveRelationFuncs.filter((func) => {
    if (!func.toFuncId) {
      return true;
    }
    if (!allFuncMap.get(func.toFuncId.toString())) {
      return false;
    }
    if (!integratedFuncMangleMap.has(func.toFuncMangledId) && !func.enableStub) {
      integratedFuncMangleMap.set(func.toFuncMangledId,
        {
          functionId: func.toFuncId.toString(),
          fileId: func.toFuncFileId.toString(),
        });
    }
    return funcKinds.includes(allFuncMap.get(func.toFuncId.toString()).kind);
  });
  funcVariables = await functionVariableSpliceFileId(
    funcVariables, {
      entryFuncFileId,
      spliceFileId,
      isOriginal: true,
      allFuncMap,
      allFileMap,
    }
  );
  let relationFuncs = await relationFuncsDeduplicate(retrieveRelationFuncs, versionId);
  const {
    variableMap, toFuncMangleMap,
  } = await getInvokeFunctionsVariable(
    functionId,
    relationFuncs,
    allFuncVariableStubs,
    versionId,
    fileId,
    true
  );
  const funcInvokesMap = new Map();
  const integratedMangled = new Set();
  relationFuncs = relationFuncs.map((func) => {
    if (func.toFuncId && !func.enableStub) {
      integratedMangled.add(`${func.toFuncFileId}_${func.toFuncMangledId}`);
    }
    const invoke = formatFuncInvoke(func, {
      allFileMap,
      variableMap,
      toFuncMangleMap,
    });
    funcInvokesMap.set(invoke.mangledId, invoke);
    return invoke;
  });
  funcVariables.mangledId = integratedMangled;
  funcVariables.stubs = {};
  await Promise.all(relationFuncs.map(async (relationFunc) => {
    if (!relationFunc.enableStub
      && relationFunc.functionId
      && relationFunc.variables
      && relationFunc.functionId.toString() !== functionId.toString()) {
      let relationFuncVariable = variableMap.get(relationFunc.functionId.toString());
      if (relationFuncVariable) {
        relationFuncVariable = relationFuncVariable.toObject({ minimize: false });
        await functionVariableSpliceFileId(relationFuncVariable,
          {
            entryFuncFileId,
            isOriginal: false,
            spliceFileId,
            allFileMap,
            allFuncMap,
          });
        mergeFunctionVariables(funcVariables, relationFuncVariable);
      }
    }
    if (relationFunc.enableStub || includeNonStub) {
      const stub = filterRelationFuncStub(relationFunc, allFuncMap, allFileMap, variableMap);
      if (!stub) {
        return;
      }
      const stubAttributes = stub['@attributes'];
      // 过滤特化函数
      if (!includeSpecializationFunc && stubAttributes && stubAttributes.includes('isTemplateSpecialization')) {
        return;
      }
      // 处理桩函数
      let newKey = stub['@key'];
      delete stub['@key'];
      stub.originalName = newKey;
      // 函数已定义/函数是静态函数,且来自于其它文件的集成函数需要拼接文件id
      if (stubAttributes
        && (stubAttributes.includes('isDefined') || stubAttributes.includes('isStatic'))
        && stub.fileId
        && stub.fileId.toString() !== entryFuncFileId
      ) {
        const stubAttachFileId = handleIntegrationVariables.getAttachFileIdOrFunctionId(newKey);
        newKey = stubAttachFileId ? `${handleIntegrationVariables.getOriginalName(newKey)}@${stubAttachFileId}` : `${newKey}@${stub.fileId}`;
      }
      if (!funcVariables.stubs[newKey]) {
        const mangled = stub['@mangled'];
        const funcInvoke = funcInvokesMap.get(mangled);
        // 处理桩函数使用的文件,保证每次取到的文件id是一致的（静态函数要保留原始文件）
        if (stubAttributes && !stubAttributes.includes('isStatic') && funcInvoke && stub.fileId && (funcInvoke.fileId.toString() >= stub.fileId.toString())) {
          stub.fileId = funcInvoke.fileId;
          stub.filePath = funcInvoke.filePath;
        }
        // 未定义取消打桩的桩函数无文件信息（未定义函数不打桩属于系统函数）
        if ((!stubAttributes || (!stubAttributes.includes('isDefined') && !stubAttributes.includes('isTemplateSpecialization'))) && !stub.enableStub) {
          stub.fileId = '';
          stub.filePath = '';
        }
        // 处理入口函数的指针目标
        if (entryStubsMap.has(newKey)) {
          const entryStub = entryStubsMap.get(newKey);
          stub.pointerTargets = entryStub.pointerTargets;
          // 手动添加的桩函数如果没有拼接的文件就默认选择入口函数文件
          if (entryStub.stubsTriggerKind === tests.stubTriggerKind.manual) {
            const attachFileId = handleIntegrationVariables.checkAttachFileIdOrFunctionId(newKey)
              ? handleIntegrationVariables.getAttachFileIdOrFunctionId(newKey) : entryFuncFileId;
            stub.fileId = attachFileId;
            stub.filePath = allFileMap.get(attachFileId).path;
          }
        }
        // 补全返回值fileId
        if (stub['%'] && stub['%']['@type']) {
          stub['%'].fileId = stub.fileId;
          stub['%'].filePath = stub.filePath;
        }
        // 补全params fileId
        if (stub.params && stub.params.length > 0) {
          stub.params.forEach((param) => {
            param.fileId = stub.fileId;
            param.filePath = stub.filePath;
          });
        }
        funcVariables.stubs[newKey] = stub;
      }
    }
  }));
  if (funcVariables.stubs) {
    funcVariables.stubs = utility.objSort(funcVariables.stubs);
  }
  if (funcVariables.variables && funcVariables.variables.global) {
    const newkeys = Object.keys(funcVariables.variables.global).sort();
    const newObj = {};
    for (let i = 0; i < newkeys.length; i += 1) {
      const globalName = newkeys[i];
      newObj[globalName] = funcVariables.variables.global[globalName];
      // 全局变量在没有自定义选择默认文件的情况下优先使用定义该全局变量的文件(如果有多个定义文件且入口函数文件也定义了此全局变量就优先使用入口函数文件)
      if (newObj[globalName]['@attributes']
        && !newObj[globalName]['@attributes'].includes('isDefined')
        && (!originalGlobals[globalName] || !originalGlobals[globalName].fileId)
        && allFileGlobals.has(globalName)) {
        const [definedFile] = allFileGlobals.get(globalName).filter((f) => f.fileId.toString() === entryFuncFileId);
        const fileGlobal = definedFile || allFileGlobals.get(globalName)[0];
        newObj[globalName].fileId = fileGlobal.fileId;
        newObj[globalName].filePath = allFileMap.get(fileGlobal.fileId).path;
        newObj[globalName]['@attributes'] = fileGlobal['@attributes'];
        newObj[globalName]['@type'] = fileGlobal['@type'];
      }
    }
    funcVariables.variables.global = newObj;
  }
  const removeVariableDoc = { $unset: {} };
  // 输入处理绝对地址表达式
  if (funcVariables.fixedAddrs) {
    const exprs = funcVariables.fixedAddrs.exprs || {};
    const bases = funcVariables.fixedAddrs.bases || {};
    Object.keys(exprs).forEach((exprName) => {
      const func = allFuncMap.get(exprs[exprName].functionId);
      const funcVariable = variableMap.get(exprs[exprName].functionId);
      if (!funcVariable || !func) {
        removeVariableDoc.$unset[exprName] = 1;
        removeVariableDoc.$unset[`data.fixedAddrs.exprs.${exprName}`] = 1;
        delete exprs[exprName];
        return;
      }
      const { fixedAddrs } = funcVariable;
      const funcFile = allFileMap.get(func.fileId.toString());
      const newExprName = exprName.replace(`@${exprs[exprName].functionId}`, '');
      if (!funcFile) {
        delete exprs[exprName];
        removeVariableDoc.$unset[exprName] = 1;
        removeVariableDoc.$unset[`data.fixedAddrs.exprs.${exprName}`] = 1;
        return;
      }
      // 来自于集成函数的表达式
      if (newExprName !== exprName) {
        if (!fixedAddrs
          || (fixedAddrs && !fixedAddrs.exprs)
          || (fixedAddrs && fixedAddrs.exprs && !fixedAddrs.exprs[newExprName])) {
          delete exprs[exprName];
          removeVariableDoc.$unset[exprName] = 1;
          removeVariableDoc.$unset[`data.fixedAddrs.exprs.${exprName}`] = 1;
          return;
        }
        // 替换原始的localtions,重新解析后会导致表达式代码行数变化
        if (fixedAddrs.exprs[newExprName].locations) {
          const { locations } = fixedAddrs.exprs[newExprName];
          Object.keys(exprs[exprName].locations).forEach((index) => {
            if (locations[index]) {
              exprs[exprName].locations[index] = locations[index];
            }
          });
        }
      }
      exprs[exprName].fileId = funcFile._id.toString();
      exprs[exprName].filePath = funcFile.path;
      exprs[exprName].functionName = func.functionName;
    });
    Object.keys(bases).forEach((baseName) => {
      if (!bases[baseName].fileId) {
        bases[baseName].fileId = entryFuncFileId;
      }
      const fixedAddrsfile = allFileMap.get(bases[baseName].fileId);
      const testedFile = allFileMap.get(entryFuncFileId);
      // 基地址来源文件不存在时赋予当前被测函数所在文件
      if (!fixedAddrsfile) {
        bases[baseName].fileId = entryFuncFileId;
        bases[baseName].filePath = testedFile.path;
        return;
      }
      bases[baseName].filePath = fixedAddrsfile.path;
    });
  }
  handleIntegrationFuncOutput(funcVariables, allFileMap, removeVariableDoc);
  // 处理integratedFuncs
  if (integratedFuncs) {
    Object.keys(integratedFuncs).forEach((mangledId) => {
      if (!integratedFuncMangleMap.has(mangledId)) {
        removeVariableDoc.$unset[`integratedFuncs.${mangledId}`] = 1;
        removeVariableDoc.$unset[`output.integratedFuncs.${mangledId}`] = 1;
        removeVariableDoc.$unset[`data.integratedFuncs.${mangledId}`] = 1;
        removeVariableDoc.$unset[`data.output.integratedFuncs.${mangledId}`] = 1;
        return;
      }
      const invokeFunc = integratedFuncMangleMap.get(mangledId);
      const func = allFuncMap.get(invokeFunc.functionId);
      const file = allFileMap.get(invokeFunc.fileId);
      const variable = variableMap.get(invokeFunc.functionId);
      if (!func || !file || !variable) {
        throw new Error(`integratedFuncs ${mangledId} func:${invokeFunc.functionId},file:${invokeFunc.fileId} ,variable not found`);
      }
      const params = [];
      if (variable.variables && variable.variables.params) {
        Object.keys(variable.variables.params).forEach((name) => {
          const param = variable.variables.params[name];
          const index = parseInt(param['@index'], 10);
          delete param['@index'];
          params[index] = { '@name': name, ...param };
        });
      }
      const ret = variable.output && variable.output['%'] ? variable.output['%'] : {};
      integratedFuncs[mangledId]['@name'] = func.functionName;
      integratedFuncs[mangledId]['@mangled'] = mangledId;
      integratedFuncs[mangledId].fileId = file._id;
      integratedFuncs[mangledId].filePath = file.path;
      integratedFuncs[mangledId].params = params;
      integratedFuncs[mangledId]['%'] = ret;
    });
    funcVariables.integratedFuncs = integratedFuncs;
  }
  // 处理输出检查项中桩函数的文件
  if (funcVariables.stubs && funcVariables.output && funcVariables.output && funcVariables.output.stubs) {
    const outputStubs = funcVariables.output.stubs;
    const inputStubs = funcVariables.stubs;
    Object.keys(outputStubs).forEach((stubName) => {
      if (inputStubs[stubName] && inputStubs[stubName].fileId && inputStubs[stubName].filePath) {
        outputStubs[stubName].fileId = inputStubs[stubName].fileId;
        outputStubs[stubName].filePath = inputStubs[stubName].filePath;
        outputStubs[stubName].mangled = inputStubs[stubName]['@mangled'];
      }
    });
  }
  // 去除冗余的指针目标
  if (funcVariables.malloc) {
    const mallocs = funcVariables.malloc;
    Object.keys(mallocs).forEach((mallocName) => {
      const malloc = mallocs[mallocName];
      if (malloc.filePath && malloc.fileId && !allFileMap.has(malloc.fileId.toString())) {
        [
          `malloc.${mallocName}`,
          `output.malloc.${mallocName}`,
          `data.output.malloc.${mallocName}`,
          `data.malloc.${mallocName}`,
        ].forEach((key) => {
          removeVariableDoc.$unset[key] = 1;
          if (funcVariables.output && funcVariables.output.malloc) {
            delete funcVariables.output.malloc[mallocName];
          }
          delete funcVariables.malloc[mallocName];
        });
      }
    });
  }
  // 同步更新被测函数variable和testcase字段
  await FunctionVariable.updateOne({ functionId: mongoose.Types.ObjectId(functionId) }, removeVariableDoc);
  await TestCase.updateMany({ functionId: mongoose.Types.ObjectId(functionId) }, removeVariableDoc);
  return funcVariables;
}

async function getRelationFuncsVariablesV2(
  functionId,
  {
    spliceFileId = true,
    allFileMap = undefined,
    allFuncMap = undefined,
    allFileGlobals,
    includeSpecializationFunc = true,
  } = {}
) {
  let funcVariables = await getFunctionVariables(functionId, true);
  if (!funcVariables) {
    throw new Error(`function variables(${functionId}) not found`);
  }
  funcVariables = funcVariables.toObject({ minimize: false });
  const { fileId, versionId, integratedFuncs } = funcVariables;
  const entryFuncFileId = fileId.toString();
  const entryStubsMap = new Map();
  const entryStubs = funcVariables.stubs || {};
  const originalGlobals = funcVariables.variables ? JSON.parse(JSON.stringify(funcVariables.variables.global || {})) : {};
  Object.keys(entryStubs).forEach((key) => {
    entryStubsMap.set(key, entryStubs[key]);
  });
  if (!allFileMap) {
    allFileMap = await fileModels.getSourceFileByVersionId(versionId, null, { _id: 1, path: 1, fileName: 1 });
  }
  if (!allFuncMap) {
    allFuncMap = await getFunctionByVersionId(versionId, {
      _id: 1, fileId: 1, functionName: 1, functionAttrs: 1, kind: 1,
    });
  }
  funcVariables = await functionVariableSpliceFileId(
    funcVariables, {
      entryFuncFileId,
      spliceFileId,
      isOriginal: true,
      allFuncMap,
      allFileMap,
    }
  );
  const funcKinds = [fileElement.elementKind.function];
  // 过滤特化函数
  if (includeSpecializationFunc) {
    funcKinds.push(fileElement.elementKind.specialFunction);
  }
  let relationFuncs = [];
  const integratedFuncMangleMap = new Map();
  const integratedFuncMangledSet = new Set();
  const functionIdSet = new Set([functionId.toString()]);
  const retrieveRelationFuncs = await invokeRelations.retrieveRelations(functionId, { initRelations: true });
  const mangledIdKeys = new Set();
  retrieveRelationFuncs.filter((relationFunc) => {
    if (!relationFunc.toFuncId) {
      return true;
    }
    if (!allFuncMap.get(relationFunc.toFuncId.toString())) {
      return false;
    }
    return funcKinds.includes(allFuncMap.get(relationFunc.toFuncId.toString()).kind);
  }).forEach((relationFunc) => {
    if (relationFunc.toFuncId && !relationFunc.enableStub) {
      integratedFuncMangleMap.set(relationFunc.toFuncMangledId,
        {
          functionId: relationFunc.toFuncId.toString(),
          fileId: relationFunc.toFuncFileId.toString(),
        });
      integratedFuncMangledSet.add(`${relationFunc.toFuncFileId}_${relationFunc.toFuncMangledId}`);
      functionIdSet.add(relationFunc.toFuncId.toString());
    }
    const toFuncData = relationFunc.toFuncId ? allFuncMap.get(relationFunc.toFuncId.toString()) : null;
    if (!mangledIdKeys.has(relationFunc.toFuncMangledId) || (toFuncData && toFuncData.functionAttrs.includes('isStatic'))) {
      mangledIdKeys.add(relationFunc.toFuncMangledId);
      relationFuncs.push(relationFunc);
    }
  });
  let variableMap = new Map();
  if (functionIdSet.size > 0) {
    variableMap = await getFunctionVariables([...functionIdSet], true);
  }
  // 移除参与集成函数中手动添加的桩函数
  variableMap.forEach((variables, key) => {
    if (key !== functionId.toString() && variables.stubs) {
      Object.keys(variables.stubs).forEach((stubName) => {
        if (variables.stubs[stubName].stubsTriggerKind
          && variables.stubs[stubName].stubsTriggerKind === tests.stubTriggerKind.manual) {
          delete variables.stubs[stubName];
        }
      });
    }
  });
  // 格式化
  const funcInvokesMap = new Map();
  relationFuncs = relationFuncs.map((func) => {
    const invoke = formatFuncInvoke(func, {
      allFileMap,
      variableMap,
    });
    funcInvokesMap.set(invoke.mangledId, invoke);
    return invoke;
  });
  funcVariables.mangledId = integratedFuncMangledSet;
  funcVariables.stubs = {};
  await Promise.all(relationFuncs.map(async (relationFunc) => {
    if (!relationFunc.enableStub
      && relationFunc.functionId
      && relationFunc.variables
      && relationFunc.functionId.toString() !== functionId.toString()) {
      let relationFuncVariable = variableMap.get(relationFunc.functionId.toString());
      if (relationFuncVariable) {
        relationFuncVariable = relationFuncVariable.toObject({ minimize: false });
        await functionVariableSpliceFileId(relationFuncVariable,
          {
            entryFuncFileId,
            isOriginal: false,
            spliceFileId,
            allFileMap,
            allFuncMap,
          });
        mergeFunctionVariables(funcVariables, relationFuncVariable);
      }
    }

    if (relationFunc.enableStub) {
      const stub = filterRelationFuncStub(relationFunc, allFuncMap, allFileMap, variableMap);
      if (!stub) {
        return;
      }
      const stubAttributes = stub['@attributes'];
      // 过滤特化函数
      if (!includeSpecializationFunc && stubAttributes && stubAttributes.includes('isTemplateSpecialization')) {
        return;
      }
      // 处理桩函数
      let newKey = stub['@key'];
      delete stub['@key'];
      stub.originalName = newKey;
      // 函数已定义/函数是静态函数,且来自于其它文件的集成函数需要拼接文件id
      if (stubAttributes
        && (stubAttributes.includes('isDefined') || stubAttributes.includes('isStatic'))
        && stub.fileId
        && stub.fileId.toString() !== entryFuncFileId
      ) {
        const stubAttachFileId = handleIntegrationVariables.getAttachFileIdOrFunctionId(newKey);
        newKey = stubAttachFileId ? `${handleIntegrationVariables.getOriginalName(newKey)}@${stubAttachFileId}` : `${newKey}@${stub.fileId}`;
      }
      if (!funcVariables.stubs[newKey]) {
        const mangled = stub['@mangled'];
        const funcInvoke = funcInvokesMap.get(mangled);
        // 处理桩函数使用的文件,保证每次取到的文件id是一致的（静态函数要保留原始文件）
        if (stubAttributes && !stubAttributes.includes('isStatic') && funcInvoke && stub.fileId && (funcInvoke.fileId.toString() >= stub.fileId.toString())) {
          stub.fileId = funcInvoke.fileId;
          stub.filePath = funcInvoke.filePath;
        }
        // 未定义取消打桩的桩函数无文件信息（未定义函数不打桩属于系统函数）
        if ((!stubAttributes || (!stubAttributes.includes('isDefined') && !stubAttributes.includes('isTemplateSpecialization'))) && !stub.enableStub) {
          stub.fileId = '';
          stub.filePath = '';
        }
        // 处理入口函数的指针目标
        if (entryStubsMap.has(newKey)) {
          const entryStub = entryStubsMap.get(newKey);
          stub.pointerTargets = entryStub.pointerTargets;
          // 手动添加的桩函数如果没有拼接的文件就默认选择入口函数文件
          if (entryStub.stubsTriggerKind === tests.stubTriggerKind.manual) {
            const attachFileId = handleIntegrationVariables.checkAttachFileIdOrFunctionId(newKey)
              ? handleIntegrationVariables.getAttachFileIdOrFunctionId(newKey) : entryFuncFileId;
            stub.fileId = attachFileId;
            stub.filePath = allFileMap.get(attachFileId).path;
          }
        }
        // 补全返回值fileId
        if (stub['%'] && stub['%']['@type']) {
          stub['%'].fileId = stub.fileId;
          stub['%'].filePath = stub.filePath;
        }
        // 补全params fileId
        if (stub.params && stub.params.length > 0) {
          stub.params.forEach((param) => {
            param.fileId = stub.fileId;
            param.filePath = stub.filePath;
          });
        }
        funcVariables.stubs[newKey] = stub;
      }
    }
  }));
  if (funcVariables.stubs) {
    funcVariables.stubs = utility.objSort(funcVariables.stubs);
  }
  if (funcVariables.variables && funcVariables.variables.global) {
    if (!allFileGlobals) {
      allFileGlobals = await getVersionGlobalVariables(versionId, 'isDefined');
    }
    const newkeys = Object.keys(funcVariables.variables.global).sort();
    const newObj = {};
    for (let i = 0; i < newkeys.length; i += 1) {
      const globalName = newkeys[i];
      newObj[globalName] = funcVariables.variables.global[globalName];
      // 全局变量在没有自定义选择默认文件的情况下优先使用定义该全局变量的文件(如果有多个定义文件且入口函数文件也定义了此全局变量就优先使用入口函数文件)
      if (newObj[globalName]['@attributes']
        && !newObj[globalName]['@attributes'].includes('isDefined')
        && (!originalGlobals[globalName] || !originalGlobals[globalName].fileId)
        && allFileGlobals.has(globalName)) {
        const [definedFile] = allFileGlobals.get(globalName).filter((f) => f.fileId.toString() === entryFuncFileId);
        const fileGlobal = definedFile || allFileGlobals.get(globalName)[0];
        newObj[globalName].fileId = fileGlobal.fileId;
        newObj[globalName].filePath = allFileMap.get(fileGlobal.fileId).path;
        newObj[globalName]['@attributes'] = fileGlobal['@attributes'];
        newObj[globalName]['@type'] = fileGlobal['@type'];
      }
    }
    funcVariables.variables.global = newObj;
  }
  const removeVariableDoc = { $unset: {} };
  // 输入处理绝对地址表达式
  if (funcVariables.fixedAddrs) {
    const exprs = funcVariables.fixedAddrs.exprs || {};
    const bases = funcVariables.fixedAddrs.bases || {};
    Object.keys(exprs).forEach((exprName) => {
      const func = allFuncMap.get(exprs[exprName].functionId);
      const funcVariable = variableMap.get(exprs[exprName].functionId);
      if (!funcVariable || !func) {
        removeVariableDoc.$unset[exprName] = 1;
        removeVariableDoc.$unset[`data.fixedAddrs.exprs.${exprName}`] = 1;
        delete exprs[exprName];
        return;
      }
      const { fixedAddrs } = funcVariable;
      const funcFile = allFileMap.get(func.fileId.toString());
      const newExprName = exprName.replace(`@${exprs[exprName].functionId}`, '');
      if (!funcFile) {
        delete exprs[exprName];
        removeVariableDoc.$unset[exprName] = 1;
        removeVariableDoc.$unset[`data.fixedAddrs.exprs.${exprName}`] = 1;
        return;
      }
      // 来自于集成函数的表达式
      if (newExprName !== exprName) {
        if (!fixedAddrs
          || (fixedAddrs && !fixedAddrs.exprs)
          || (fixedAddrs && fixedAddrs.exprs && !fixedAddrs.exprs[newExprName])) {
          delete exprs[exprName];
          removeVariableDoc.$unset[exprName] = 1;
          removeVariableDoc.$unset[`data.fixedAddrs.exprs.${exprName}`] = 1;
          return;
        }
        // 替换原始的localtions,重新解析后会导致表达式代码行数变化
        if (fixedAddrs.exprs[newExprName].locations) {
          const { locations } = fixedAddrs.exprs[newExprName];
          Object.keys(exprs[exprName].locations).forEach((index) => {
            if (locations[index]) {
              exprs[exprName].locations[index] = locations[index];
            }
          });
        }
      }
      exprs[exprName].fileId = funcFile._id.toString();
      exprs[exprName].filePath = funcFile.path;
      exprs[exprName].functionName = func.functionName;
    });
    Object.keys(bases).forEach((baseName) => {
      if (!bases[baseName].fileId) {
        bases[baseName].fileId = entryFuncFileId;
      }
      const fixedAddrsfile = allFileMap.get(bases[baseName].fileId);
      const testedFile = allFileMap.get(entryFuncFileId);
      // 基地址来源文件不存在时赋予当前被测函数所在文件
      if (!fixedAddrsfile) {
        bases[baseName].fileId = entryFuncFileId;
        bases[baseName].filePath = testedFile.path;
        return;
      }
      bases[baseName].filePath = fixedAddrsfile.path;
    });
  }
  // 去除冗余的指针目标
  if (funcVariables.malloc) {
    const mallocs = funcVariables.malloc;
    Object.keys(mallocs).forEach((mallocName) => {
      const malloc = mallocs[mallocName];
      if (malloc.filePath && malloc.fileId && !allFileMap.has(malloc.fileId.toString())) {
        [
          `malloc.${mallocName}`,
          `output.malloc.${mallocName}`,
          `data.output.malloc.${mallocName}`,
          `data.malloc.${mallocName}`,
        ].forEach((key) => {
          removeVariableDoc.$unset[key] = 1;
          if (funcVariables.output && funcVariables.output.malloc) {
            delete funcVariables.output.malloc[mallocName];
          }
          delete funcVariables.malloc[mallocName];
        });
      }
    });
  }
  // 处理integratedFuncs
  if (integratedFuncs) {
    Object.keys(integratedFuncs).forEach((mangledId) => {
      if (!integratedFuncMangleMap.has(mangledId)) {
        removeVariableDoc.$unset[`integratedFuncs.${mangledId}`] = 1;
        removeVariableDoc.$unset[`output.integratedFuncs.${mangledId}`] = 1;
        removeVariableDoc.$unset[`data.integratedFuncs.${mangledId}`] = 1;
        removeVariableDoc.$unset[`data.output.integratedFuncs.${mangledId}`] = 1;
        return;
      }
      const invokeFunc = integratedFuncMangleMap.get(mangledId);
      const func = allFuncMap.get(invokeFunc.functionId);
      const file = allFileMap.get(invokeFunc.fileId);
      const variable = variableMap.get(invokeFunc.functionId);
      if (!func || !file || !variable) {
        throw new Error(`integratedFuncs ${mangledId} func:${invokeFunc.functionId},file:${invokeFunc.fileId} ,variable not found`);
      }
      const params = [];
      if (variable.variables && variable.variables.params) {
        Object.keys(variable.variables.params).forEach((name) => {
          const param = variable.variables.params[name];
          const index = parseInt(param['@index'], 10);
          delete param['@index'];
          params[index] = { '@name': name, ...param };
        });
      }
      const ret = variable.output && variable.output['%'] ? variable.output['%'] : {};
      integratedFuncs[mangledId]['@name'] = func.functionName;
      integratedFuncs[mangledId]['@mangled'] = mangledId;
      integratedFuncs[mangledId].fileId = file._id;
      integratedFuncs[mangledId].filePath = file.path;
      integratedFuncs[mangledId].params = params;
      integratedFuncs[mangledId]['%'] = ret;
    });
    funcVariables.integratedFuncs = integratedFuncs;
  }
  handleIntegrationFuncOutput(funcVariables, allFileMap, removeVariableDoc);
  // 去除没有返回值的空结构
  if (funcVariables.output && funcVariables.output['%'] && Object.keys(funcVariables.output['%']).length === 0) {
    delete funcVariables.output['%'];
  }
  // 同步更新被测函数variable和testcase字段
  if (Object.keys(removeVariableDoc.$unset).length > 0) {
    await FunctionVariable.updateOne({ functionId: mongoose.Types.ObjectId(functionId) }, removeVariableDoc);
    await TestCase.updateMany({ functionId: mongoose.Types.ObjectId(functionId) }, removeVariableDoc);
  }
  return funcVariables;
}

async function getUsedTypes(variable, testcase) {
  const types = {};
  function rescGetType(obj, usedTypes, fileId) {
    if (utility.isObjectType(obj)) {
      let type = null;
      if (!utility.isEmpty(obj['@elementType'])) {
        type = obj['@elementType'];
      } else if (!utility.isEmpty(obj['@type'])) {
        type = obj['@type'];
      } else if (!utility.isEmpty(obj['@pointeeType'])) {
        type = obj['@pointeeType'];
      }
      fileId = (obj.fileId || fileId).toString();
      if (type) {
        if (usedTypes[fileId] === undefined) {
          usedTypes[fileId] = [];
        }
        usedTypes[fileId].push(type);
      }
      Object.values(obj).forEach((item) => {
        rescGetType(item, usedTypes, fileId);
      });
    } else if (utility.isArrayType(obj)) {
      obj.forEach((item) => {
        rescGetType(item, usedTypes, fileId);
      });
    }
  }
  const usedTypes = {};
  rescGetType(variable, usedTypes, variable.fileId);
  rescGetType(testcase, usedTypes, variable.fileId);
  const breakPoints = await BreakPoint.find({ testcaseId: testcase._id }, ['fileId', 'expandedVariables']);
  breakPoints.forEach((breakPoint) => {
    rescGetType(breakPoint.expandedVariables, usedTypes, breakPoint.fileId);
  });
  await utility.arrayChunkOperation(Object.keys(usedTypes), 10, async (fileId) => {
    types[fileId] = {};
    let fileTypes = await fileModels.getFileTypeSystemByFile(fileId);
    // 项目外文件类型从当前入口函数中取
    if (!fileTypes || utility.isEmpty(fileTypes.types)) {
      fileTypes = await fileModels.getFileTypeSystemByFile(variable.fileId);
    }
    fileTypes = fileTypes.types;
    const typesSet = new Set([...usedTypes[fileId]]);
    while (typesSet.size > 0) {
      const type = typesSet.values().next().value;
      typesSet.delete(type);
      if (fileTypes[type] === undefined || types[fileId][type] !== undefined) {
        // eslint-disable-next-line no-continue
        continue;
      }
      types[fileId][type] = fileTypes[type];
      if (fileTypes[type]['@canonical'] && types[fileId][fileTypes[type]['@canonical']] === undefined) {
        typesSet.add(fileTypes[type]['@canonical']);
      }
      if (fileTypes[type]['@unqualified'] && types[fileId][fileTypes[type]['@unqualified']] === undefined) {
        typesSet.add(fileTypes[type]['@unqualified']);
      }
      const childUsedTypes = {};
      rescGetType(fileTypes[type], childUsedTypes, fileId);
      Object.values(childUsedTypes).forEach((ts) => {
        ts.forEach((t) => {
          if (types[fileId][t] === undefined) {
            typesSet.add(t);
          }
        });
      });
    }
  });
  return types;
}

/**
 * 更新函数调用覆盖率
 * @param {String} functionId 函数Id
 * @param {Object} functionCfg
 * @returns {Promise}
 */
async function updateFunctionInvokeCoverage(functionId, functionCfg = null) {
  functionId = mongoose.Types.ObjectId(functionId);
  if (!functionCfg) {
    functionCfg = await getFunctionCFG(functionId);
  }
  if (!functionCfg) {
    return;
  }
  const { invokedFuncCovered, invokeCovered } = getInvokeCoverage(functionCfg);
  const originalFunc = await Func.findOneAndUpdate(
    { _id: functionId },
    {
      $set: {
        invokedFuncCovered,
        invokeCovered,
      },
    }
  );
  if (!originalFunc || originalFunc.collectCoverage === false) {
    return;
  }
  const functionData = {
    functionId,
    versionId: originalFunc.versionId,
    creatorId: originalFunc.creatorId,
    fileId: originalFunc.fileId,
    collectCoverage: originalFunc.collectCoverage,
  };
  if (originalFunc.invokedFuncTotal) {
    functionData.invokedFuncCoverage = invokedFuncCovered / originalFunc.invokedFuncTotal;
  }
  if (originalFunc.invokeTotal) {
    functionData.invokeCoverage = invokeCovered / originalFunc.invokeTotal;
  }
  publisher.publishFunctionMessage('update', functionData);
  await common.increaseFunction(functionId, {
    $inc: {
      invokedFuncCovered: invokedFuncCovered - originalFunc.invokedFuncCovered || 0,
      invokeCovered: invokeCovered - originalFunc.invokeCovered || 0,
    },
  });
}

/**
 * 更新函数覆盖调用覆盖分母
 * @param {Array<String>} functionIds 函数Id数组
 * @param {String} versionId 版本Id
 * @returns {Promise}
 */
async function updateInvokeCoverageTotal(functionIds, versionId) {
  const version = await projectVersions.getVersion(versionId);
  if (!version || version.versionType !== projectVersions.versionType.integrationTest) {
    return;
  }
  // 循环外获取，提升性能
  const funcRelations = await functionRelations.getRelationsByVersion(versionId);
  const funcs = await getFunctions(
    versionId,
    {
      kinds: [fileElement.elementKind.function, fileElement.elementKind.specialFunction],
      projection: { _id: 1, kind: 1, insideProject: 1 },
    }
  );
  const specFuncIds = new Set();
  const insideProjectFuncIds = new Set();
  funcs.forEach((func) => {
    if (func.kind === fileElement.elementKind.specialFunction) {
      specFuncIds.add(func._id.toString());
    }
    if (func.insideProject) {
      insideProjectFuncIds.add(func._id.toString());
    }
  });
  functionIds = utility.arrayDeduplicate(functionIds.map((functionId) => functionId.toString()));
  let queue = Promise.resolve();
  utility.arrayChunk(functionIds, 100).forEach((ids) => {
    queue = queue.then(
      () => Promise.all(ids.map(async (functionId) => {
        functionId = mongoose.Types.ObjectId(functionId);
        let relations = await invokeRelations.retrieveRelations(functionId, { includeCtor: false });
        if (relations.length === 0) {
          relations = await functionRelations.retrieveRelations(
            functionId,
            versionId,
            funcRelations
          );
          relations = JSON.parse(JSON.stringify(relations));
          relations = invokeRelations.stubFuncsByDefaultDepth(functionId, relations, specFuncIds, version.stubDepth);
          relations = await invokeRelations.filterRelations(functionId, relations, { includeCtor: false });
        }
        const toFuncIdSet = new Set();
        const stubMangled = new Set();
        const undefinedMangled = new Set();
        relations.forEach((relation) => {
          // 插桩函数过滤
          if (relation.enableStub) {
            stubMangled.add(relation.toFuncMangledId);
            return;
          }
          // 未定义函数过滤
          if (!relation.toFuncId) {
            undefinedMangled.add(relation.toFuncMangledId);
            return;
          }
          // 函数指针过滤
          if (relation.toFuncPointer === true || relation.toFuncCtor === true) {
            return;
          }
          // 循环调用过滤
          if (relation.toFuncId.toString() === functionId.toString()) {
            return;
          }
          // 项目外函数过滤
          if (!insideProjectFuncIds.has(relation.toFuncId.toString())) {
            return;
          }
          toFuncIdSet.add(relation.toFuncId.toString());
        });
        const callExprs = await FunctionCFG.find(
          {
            functionId: { $in: [...[...toFuncIdSet].map((id) => mongoose.Types.ObjectId(id)), functionId] },
            callexpr: { $exists: true },
          }
        );
        let invokeTotal = 0;
        callExprs.forEach((callExpr) => {
          if (!utility.isArrayType(callExpr.callexpr)) {
            return;
          }
          callExpr.callexpr.forEach((expr) => {
            // 未定义函数以及打桩函数不计入调用覆盖
            if (expr.mangled && (stubMangled.has(expr.mangled) || undefinedMangled.has(expr.mangled))) {
              return;
            }
            invokeTotal += 1;
          });
        });
        // +1是因为要包含入口函数本身
        const invokedFuncTotal = toFuncIdSet.size + 1;
        const originalFunc = await Func.findOneAndUpdate(
          { _id: functionId },
          {
            $set: {
              invokeTotal,
              invokedFuncTotal,
            },
          }
        );
        if (!originalFunc) {
          return Promise.resolve();
        }
        if (invokeTotal === originalFunc.invokeTotal && invokedFuncTotal === originalFunc.invokedFuncTotal) {
          return Promise.resolve();
        }
        const incDoc = {
          $inc: {
            invokeTotal: invokeTotal - originalFunc.invokeTotal || 0,
            invokedFuncTotal: invokedFuncTotal - originalFunc.invokedFuncTotal || 0,
          },
        };
        if (originalFunc.testCaseCreate) {
          await removeResultOfExecution(functionId);
        } else {
          const data = {
            functionId,
            fileId: originalFunc.fileId,
            versionId: originalFunc.versionId,
            creatorId: originalFunc.creatorId,
            collectCoverage: originalFunc.collectCoverage,
            invokeCoverage: null,
            invokedFuncCoverage: null,
          };
          if (invokeTotal > 0) {
            data.invokeCoverage = 0;
          }
          if (invokedFuncTotal > 0) {
            data.invokedFuncCoverage = 0;
          }
          publisher.publishFunctionMessage('update', data);
        }
        if (originalFunc.collectCoverage !== false) {
          await common.increaseFunction(functionId, incDoc);
        }
        toFuncIdSet.clear();
        return Promise.resolve();
      }))
    );
  });
  await queue;
}

/**
 * 更新函数覆盖调用覆盖分子
 * @param {String} testId 测试用例Id
 * @param {String} versionId 版本Id
 * @param {Object} coverage 覆盖数据
 * @returns {Promise}
 */
async function updateCoveredInvokes(testId, versionId, coverage) {
  testId = mongoose.Types.ObjectId(testId);
  let testResourceIns = TestCase;
  let testDb = await testResourceIns.findOne(testId);
  if (!testDb) {
    testResourceIns = TestCaseGroup;
    testDb = await testResourceIns.findOne(testId);
  }
  if (!testDb) {
    return;
  }
  if (!coverage.functions || coverage.functions.length === 0) {
    return;
  }
  let invokedFuncCovered = 0;
  let invokeCovered = 0;
  const covered = {};
  const relations = await invokeRelations.retrieveRelations(testDb.functionId, { initRelations: true, includeCtor: false });
  const toFuncIdSet = new Set();
  toFuncIdSet.add(testDb.functionId.toString());
  const stubMangled = new Set();
  const undefinedMangled = new Set();
  relations.forEach((relation) => {
    if (relation.enableStub) {
      stubMangled.add(relation.toFuncMangledId);
      return;
    }
    if (!relation.toFuncId) {
      undefinedMangled.add(relation.toFuncMangledId);
      return;
    }
    if (relation.toFuncPointer === true || relation.toFuncCtor === true) {
      return;
    }
    if (relation.toFuncId.toString() === testDb.functionId.toString()) {
      return;
    }
    toFuncIdSet.add(relation.toFuncId.toString());
  });
  await Promise.all(coverage.functions.map(async (func) => {
    if (!func.count) {
      return;
    }
    const file = await File.findOne(
      { versionId: mongoose.Types.ObjectId(versionId), path: func.path }
    );
    if (!file) {
      return;
    }
    const funcDb = await Func.findOne({ fileId: file._id, mangledId: func.mangled });
    if (!funcDb || !funcDb.invokedFuncTotal || !toFuncIdSet.has(funcDb._id.toString()) || !funcDb.insideProject) {
      return;
    }
    invokedFuncCovered += 1;
    let field = `invokes|${funcDb._id}`;
    if (func.testcases && utility.isArrayType(func.testcases)) {
      func.testcases.forEach((id) => {
        if (!covered[id]) {
          covered[id] = {
            $set: {
              versionId: mongoose.Types.ObjectId(versionId),
            },
          };
        }
        covered[id].$set[`coveredFields.${field}`] = func.count;
      });
    }
    if (!func.callexpr || func.callexpr.length === 0) {
      return;
    }
    // 判断是否是有效的callexpr
    const funcVariable = await FunctionCFG.findOne({ functionId: funcDb._id }, ['callexpr']);
    const validCallExprSet = new Set();
    if (funcVariable && funcVariable.callexpr && utility.isArrayType(funcVariable.callexpr)) {
      funcVariable.callexpr.forEach((callExpr) => {
        // 未定义函数以及打桩函数不计入调用覆盖
        if (callExpr.mangled && (stubMangled.has(callExpr.mangled) || undefinedMangled.has(callExpr.mangled))) {
          return;
        }
        validCallExprSet.add(callExpr.id);
      });
    }
    func.callexpr.forEach((expr) => {
      if (!expr.count || !validCallExprSet.has(expr.id) || !funcDb.invokeTotal) {
        return;
      }
      invokeCovered += 1;
      field = `invokes|${funcDb._id}|callexpr|${expr.id}`;
      if (expr.testcases && utility.isArrayType(expr.testcases)) {
        expr.testcases.forEach((id) => {
          if (!covered[id]) {
            covered[id] = {
              $set: {
                versionId: mongoose.Types.ObjectId(versionId),
              },
            };
          }
          covered[id].$set[`coveredFields.${field}`] = expr.count;
        });
      }
    });
  }));
  await testResourceIns.updateOne({ _id: testId }, { invokedFuncCovered, invokeCovered });
  await TestCaseCoveredField.bulkWrite(Object.keys(covered).map((testId) => ({
    updateOne: {
      filter: { functionId: testDb.functionId, testcaseId: mongoose.Types.ObjectId(testId) },
      update: covered[testId],
      upsert: true,
    },
  })));
  await updateFunctionInvokeCoverage(testDb.functionId);
}

/**
 * 导入测试用例更新variables
 * @param {string} functionId 函数id
 * @param {object} funcVariables 导入的variable
 * @param {boolean} importTestCase true:用例导入,false:用例迁移
 * @param {Map} allFilePathOfVersion 版本下所有文件path map
 */
async function integrationTestUpdateFunctionVariableByVariable(
  functionId,
  funcVariables,
  importTestCase = false,
  allFilePathOfVersion
) {
  functionId = mongoose.Types.ObjectId(functionId);
  let resetFunctionVariable = false;
  const {
    malloc,
    output,
    fixedAddrs,
    variables,
    stubs,
    integratedFuncs,
    existingTestCaseCount,
  } = funcVariables;
  if (importTestCase) {
    resetFunctionVariable = existingTestCaseCount === 0;
  }
  const func = await Func.findById(mongoose.Types.ObjectId(functionId));
  const currentFuncFileId = func.fileId.toString();
  const inPromises = [];
  const outPromises = [];
  const mallocVariables = [];
  const newStubs = {};
  const stubPointerTargets = [];
  const mallocOutputVariables = [];
  const fixedAddrsOutputVariables = [];
  const globalOutputVariables = [];
  const inputSelfDefinedGlobals = [];
  const staticOutputVariables = [];
  const newExprs = {};
  const addNewExprs = { $set: {} };
  //  如果不存在测试用例，重置function variable中的output
  if (resetFunctionVariable) {
    const doc = { $unset: {} };
    doc.$unset['output.fixedAddrs'] = 1;
    doc.$unset['output.global'] = 1;
    doc.$unset['output.statics'] = 1;
    doc.$unset['output.malloc'] = 1;
    await FunctionVariable.updateOne({ functionId }, doc);
  }
  let currentFuncVariable = (await getFunctionVariables(functionId)).toJSON();
  const testedFuncExprs = currentFuncVariable.fixedAddrs && currentFuncVariable.fixedAddrs.exprs ? currentFuncVariable.fixedAddrs.exprs : {};
  // 处理全局变量回填信息
  if (variables && variables.global) {
    Object.keys(variables.global).forEach((globalName) => {
      // 处理外部全局变量
      const globalTypeData = variables.global[globalName];
      if (globalTypeData.isExternalGlobal) {
        const { filePath } = globalTypeData;
        if (handleIntegrationVariables.checkAttachFileIdOrFunctionId(globalName)) {
          globalName = globalName.replace(handleIntegrationVariables.checkAttachFileIdOrFunctionId(globalName)[0], '');
        }
        inPromises.push((async () => {
          const file = allFilePathOfVersion.get(filePath);
          if (file) {
            await replaceExternalGlobals(
              functionId,
              [{ fileId: file._id, fullname: globalName }]
            );
          }
        })());
      }
      // 处理自定义全局变量
      if (globalTypeData.selfDefined) {
        const obj = {
          globalName,
          typeName: globalTypeData['@type'],
        };
        if (globalTypeData['@elementType']) {
          obj.typeName = globalTypeData['@elementType'];
          obj.arrayLength = globalTypeData['@length'];
        }
        inputSelfDefinedGlobals.push(obj);
      }
    });
  }
  // 处理输出检查项自定义全局变量
  if (output && output.global && !resetFunctionVariable) {
    const globals = output.global;
    Object.keys(globals).forEach((globalName) => {
      if (!globals[globalName].selfDefined) {
        return;
      }
      globalOutputVariables.push(globalName);
    });
  }
  // 解析指针目标
  if (malloc) {
    Object.keys(malloc).forEach((variableName) => {
      const variable = malloc[variableName];
      if (handleIntegrationVariables.checkAttachFileIdOrFunctionId(variableName)) {
        variableName = variableName.replace(handleIntegrationVariables.checkAttachFileIdOrFunctionId(variableName)[0], '');
      }
      mallocVariables.push({
        variableName,
        typeName: variable['@type'],
        elementTypeName: variable['@elementType'],
        length: variable['@length'],
        filePath: variable.filePath,
      });
    });
  }
  /**
   * 绝对地址目标回写到functionVariables
   */
  if (fixedAddrs && fixedAddrs.bases) {
    Object.keys(fixedAddrs.bases).forEach((baseName) => {
      let fixedAddrbasesFileId = fixedAddrs.bases[baseName].fileId;
      const {
        pointerTargets, length, filePath, assignmentKind,
      } = fixedAddrs.bases[baseName];
      if (handleIntegrationVariables.checkAttachFileIdOrFunctionId(baseName)) {
        baseName = baseName.replace(handleIntegrationVariables.checkAttachFileIdOrFunctionId(baseName)[0], '');
      }
      if (filePath && allFilePathOfVersion.has(filePath)) {
        fixedAddrbasesFileId = allFilePathOfVersion.get(filePath)._id.toString();
      }
      inPromises.push(addFixedAddressBases(functionId, baseName, length, pointerTargets, fixedAddrbasesFileId, assignmentKind));
    });
  }
  if (fixedAddrs && fixedAddrs.exprs) {
    Object.keys(fixedAddrs.exprs).forEach((exprName) => {
      if (!testedFuncExprs[exprName]) {
        newExprs[exprName] = fixedAddrs.exprs[exprName];
      } else {
        addNewExprs.$set[`fixedAddrs.exprs.${exprName}.enabled`] = 1;
      }
    });
  }
  if (stubs) {
    currentFuncVariable.stubs = currentFuncVariable.stubs || {};
    Object.keys(stubs).forEach((stubName) => {
      const stub = stubs[stubName];
      const stubFilePath = stubName.indexOf('@') !== -1 ? stubName.substring(stubName.lastIndexOf('@') + 1, stubName.length) : '';
      // 参与集成函数的文件不存在需要清除掉掉该桩函数
      if (stubFilePath && !allFilePathOfVersion.has(stubFilePath)) {
        return;
      }
      // 将手动添加的桩函数写入到关系表中,手动添加的桩函数桩函数名不会出现@文件路径的情况
      if (stub.stubsTriggerKind && stub.stubsTriggerKind === tests.stubTriggerKind.manual && !currentFuncVariable.stubs[stubName]) {
        inPromises.push(invokeRelations.createRelationsForManualStub(functionId, stubName, stubName));
        newStubs[stubName] = stub;
        return;
      }
      // 处理桩函数指针目标
      if (stub.pointerTargets) {
        // eslint-disable-next-line no-inner-declarations
        function handleStubPointTargets(sourcePointTarget, targetPointTarget = {}, stubName) {
          Object.keys(sourcePointTarget).forEach((variableName) => {
            if (targetPointTarget[variableName]) {
              return;
            }
            const pointerTarget = sourcePointTarget[variableName];
            stubPointerTargets.push({
              stubName,
              variableName,
              elementTypeName: pointerTarget['@elementType'],
              typeName: pointerTarget['@type'],
              length: pointerTarget['@length'],
            });
          });
        }
        // 桩函数有拼接文件路径的需要转换成文件id
        let newStubName = stubName;
        if (stubFilePath) {
          newStubName = stubName.replace(stubFilePath, allFilePathOfVersion.get(stubFilePath)._id.toString());
        }
        // 合并桩函数指针目标
        if (currentFuncVariable.stubs[newStubName]) {
          handleStubPointTargets(stub.pointerTargets, currentFuncVariable.stubs[newStubName].pointerTargets, newStubName);
          return;
        }
        // 回填整个集成函数包括指针目标
        if (stubFilePath) {
          newStubs[newStubName] = { pointerTargets: stub.pointerTargets };
        }
      }
    });
  }
  const newIntegratedFuncs = {};
  if (integratedFuncs) {
    currentFuncVariable[integratedFuncKey] = currentFuncVariable[integratedFuncKey] || {};
    Object.keys(integratedFuncs).forEach((mangledId) => {
      const integratedFunc = integratedFuncs[mangledId];
      if (!currentFuncVariable[integratedFuncKey][mangledId]) {
        newIntegratedFuncs[`${integratedFuncKey}.${mangledId}`] = integratedFunc;
        return;
      }
      if (!integratedFunc.pointerTargets) {
        return;
      }
      if (!currentFuncVariable[integratedFuncKey][mangledId].pointerTargets) {
        newIntegratedFuncs[`${integratedFuncKey}.${mangledId}.pointerTargets`] = integratedFunc.pointerTargets;
        return;
      }
      Object.keys(integratedFunc.pointerTargets).forEach((variableName) => {
        if (currentFuncVariable[integratedFuncKey][mangledId].pointerTargets[variableName]) {
          return;
        }
        newIntegratedFuncs[`${integratedFuncKey}.${mangledId}.pointerTargets.${variableName}`] = integratedFunc.pointerTargets[variableName];
      });
    });
    if (output[integratedFuncKey]) {
      currentFuncVariable.output[integratedFuncKey] = currentFuncVariable.output[integratedFuncKey] || {};
      Object.keys(output[integratedFuncKey]).forEach((mangledId) => {
        if (!currentFuncVariable.output[integratedFuncKey][mangledId]) {
          newIntegratedFuncs[`output.${integratedFuncKey}.${mangledId}`] = output[integratedFuncKey][mangledId];
        }
      });
    }
  }
  if (resetFunctionVariable) {
    // globals output
    if (output && output.global) {
      Object.keys(output.global).forEach((variableName) => {
        globalOutputVariables.push(variableName);
      });
    }
    // statics output
    if (output && output.statics) {
      Object.keys(output.statics).forEach((variableName) => {
        staticOutputVariables.push(variableName);
      });
    }
    if (output && output.malloc) {
      Object.keys(output.malloc).forEach((variableName) => {
        const variable = output.malloc[variableName];
        if (handleIntegrationVariables.checkAttachFileIdOrFunctionId(variableName)) {
          variableName = variableName.replace(handleIntegrationVariables.checkAttachFileIdOrFunctionId(variableName)[0], '');
        }
        mallocOutputVariables.push({
          variableName,
          typeName: variable['@type'],
          elementTypeName: variable['@elementType'],
          length: variable['@length'],
        });
      });
    }
    // addOutputFixedAddress
    if (output && output.fixedAddrs && fixedAddrs.bases) {
      Object.keys(output.fixedAddrs).forEach((variableName) => {
        fixedAddrsOutputVariables.push(variableName);
      });
    }
  }

  // 处理绝对地址表达式
  const exprsPromises = [];
  Object.keys(newExprs).forEach((exprName) => {
    let exprsFuncId = functionId.toString();
    let newExprName = exprName;
    const exprss = exprName.split('@');
    // 当拼接函数id的时候处理
    if (handleIntegrationVariables.checkAttachFileIdOrFunctionId(exprName)) {
      [newExprName, exprsFuncId] = exprss;
    }
    exprsPromises.push((async () => {
      // 回填解析的原始的类型
      const originalVariable = await FunctionOriginalVariable.findOne({ functionId: mongoose.Types.ObjectId(exprsFuncId) });
      const originalExpr = originalVariable && originalVariable.fixedAddrs && originalVariable.fixedAddrs.exprs
        ? originalVariable.fixedAddrs.exprs[newExprName] : null;
      if (originalExpr) {
        addNewExprs.$set[`fixedAddrs.exprs.${exprName}`] = originalExpr;
      }
      // 测试用例导入的时候拼接的是exprname@filePath@mangleId
      if (!originalExpr
        && exprss.length === 3
        && allFilePathOfVersion.has(exprss[1])) {
        [newExprName] = exprss;
        const mangledId = exprss[2];
        const invokeFunc = await Func.findOne({ mangledId, fileId: allFilePathOfVersion.get(exprss[1])._id }, { _id: 1 });
        if (!invokeFunc) {
          return;
        }
        delete newExprs[exprName].fileId;
        delete newExprs[exprName].filePath;
        delete newExprs[exprName].functionId;
        delete newExprs[exprName].functionName;
        addNewExprs.$set[`fixedAddrs.exprs.${newExprName}@${invokeFunc._id.toString()}`] = newExprs[exprName];
      }
    })());
  });
  await Promise.all(exprsPromises);
  // 处理桩函数加检查项
  if (currentFuncVariable.stubs && output.stubs) {
    Object.keys(output.stubs).forEach((stubName) => {
      if (!currentFuncVariable.stubs[stubName] && !newStubs[stubName]) {
        return;
      }
      // eslint-disable-next-line no-use-before-define
      outPromises.push(addStubParamsOutput(functionId, stubName));
    });
  }
  inPromises.push(FunctionVariable.updateOne({ functionId }, addNewExprs));
  inPromises.push(FunctionVariable.updateOne({ functionId }, { $set: newIntegratedFuncs }));
  inPromises.push(updateStubs(functionId, newStubs));
  inPromises.push(addStubPointerTargets(functionId, stubPointerTargets));
  inPromises.push(addMallocVariables(functionId, mallocVariables));
  await Promise.all(inPromises);
  if (inputSelfDefinedGlobals.length > 0) {
    // eslint-disable-next-line no-use-before-define
    await updateSelfDefinedGlobals(functionId, inputSelfDefinedGlobals);
  }
  currentFuncVariable = (await getFunctionVariables(functionId)).toJSON();
  outPromises.push(addOutputMallocVariables(functionId, mallocOutputVariables));
  // 处理绝对地址输出
  [...new Set(fixedAddrsOutputVariables)].forEach((variableName) => {
    const variable = utility.getValueByKeyPath(currentFuncVariable, `fixedAddrs.bases.${variableName}`);
    if (variable) {
      outPromises.push(addOutputFixedAddress(functionId, variableName, variable));
    }
  });
  // global
  [...new Set(globalOutputVariables)].forEach((variableName) => {
    const [variableName1] = variableName.split('@');
    const variable = utility.getValueByKeyPath(currentFuncVariable, `variables.global.${variableName1}`);
    if (variable) {
      outPromises.push(addOutputGlobalVariable(functionId, handleIntegrationVariables.handleGlobalOutputFileId(
        currentFuncFileId,
        variableName
      ),
      variable));
    }
  });
  // static
  [...new Set(staticOutputVariables)].forEach((variableName) => {
    const [variableName1] = variableName.split('@');
    const variable = utility.getValueByKeyPath(currentFuncVariable, `variables.statics.${variableName1}`);
    if (variable) {
      outPromises.push(addOutputStaticsVariable(functionId, handleIntegrationVariables.handleStaticsOutputFunctionId(
        functionId,
        variableName
      ),
      variable));
    }
  });
  await Promise.all(outPromises);
}

async function getTestCountByFunctions(functionIds) {
  if (utility.isEmpty(functionIds)) {
    return 0;
  }
  if (!utility.isArrayType(functionIds)) {
    functionIds = [functionIds];
  }
  functionIds = functionIds.map((id) => mongoose.Types.ObjectId(id));
  return TestCase.countDocuments({ functionId: { $in: functionIds } });
}

async function removeCfgCoverageWithTestId(functionId, testId) {
  const cfg = await FunctionCFG.findOne({ functionId });
  const doc = { $unset: {} };
  // 兼容老的数据
  if (cfg && cfg.coveredFields && cfg.coveredFields[testId]) {
    doc.$unset[`coveredFields.${testId}`] = 1;
    cfg.coveredFields[testId].forEach((field) => {
      doc.$unset[field] = 1;
    });
  }
  return Promise.all([
    FunctionCFG.updateOne({ functionId }, doc),
    TestCaseCoveredField.deleteMany({ testcaseId: mongoose.Types.ObjectId(testId) }),
  ]);
}
/**
 * 更新测试用例中桩函数状态
 * @param {String|Objectid} functionId 入口函数id
 * @param {Object[]} testCases 需要清除的测试用例，默认清除入口函数所有用例
 * @param {String} mangledId
 */
async function integrationUpdateTestCaseStubs(functionId, testCases = [], mangledId = undefined) {
  // 更新测试用例中的stubs
  const { stubs } = await getRelationFuncsVariables(functionId);
  if (stubs) {
    const promises = [];
    if (testCases.length === 0) {
      testCases = await TestCase.find({ functionId: mongoose.Types.ObjectId(functionId) });
    }
    testCases.forEach((testcase) => {
      if (testcase.data && testcase.data.stubs) {
        const doc = { $unset: {}, $set: {} };
        Object.keys(testcase.data.stubs).forEach((stubName) => {
          if (!stubs[stubName]) {
            doc.$unset[`data.stubs.${stubName}`] = 1;
            // 清除桩函数检查项
            doc.$unset[`data.output.stubs.${stubName}`] = 1;
          }
          // 清除测试用例中切换文件的桩函数的值
          if (mangledId && stubs[stubName] && stubs[stubName]['@mangled'] === mangledId) {
            doc.$unset[`data.stubs.${stubName}`] = 1;
            // 清除桩函数检查项
            doc.$unset[`data.output.stubs.${stubName}`] = 1;
          }
        });
        promises.push(TestCase.updateOne({ _id: testcase._id }, doc));
      }
    });
    await Promise.all(promises);
  }
}
/**
 * 批量修改集成函数打桩状态，手动添加函数和特化函数不能改变打桩状态
 * @param {String} functionId 入口函数
 * @param {String} versionId 版本id
 * @param {Boolean} stubStatus 打桩状态
 * @param {Number} depth 打桩层级
 */
async function updateAllRelationStubStatusByFunctionId(functionId, versionId, stubStatus = undefined, depth = undefined) {
  const relationsFuncs = await invokeRelations.retrieveRelations(functionId, { initRelations: true, completeDepth: true });
  let isDefineFuncs = [];
  // 未定义函数，或定义函数当前选择为调用文件
  let notDefineFuncs = [];
  let status = stubStatus === undefined ? false : stubStatus;

  const funcs = await getFunctions(versionId, { kinds: [fileElement.elementKind.specialFunction], projection: { _id: 1 } });
  const specialFunctionIds = new Set();
  funcs.forEach((func) => {
    specialFunctionIds.add(func._id.toString());
  });

  relationsFuncs.forEach((func) => {
    const toFuncId = func.toFuncId ? func.toFuncId.toString() : '';
    // 特化函数/手动添加的桩函数不可以改变打桩状态
    if ((toFuncId && specialFunctionIds.has(toFuncId)) || func.toFuncName === func.toFuncMangledId) {
      return;
    }
    depth = depth ? parseInt(depth, 10) : null;
    // 层级不符合函数过滤
    if (depth && !func.depth.depths.has(depth)) {
      return;
    }
    if (!func.enableStub && stubStatus === undefined) {
      status = true;
    }
    if (toFuncId) {
      isDefineFuncs.push(func);
      return;
    }
    notDefineFuncs.push(func);
  });
  notDefineFuncs = notDefineFuncs.filter((func) => func.enableStub !== status);
  isDefineFuncs = isDefineFuncs.filter((func) => func.enableStub !== status);
  await Promise.all(notDefineFuncs.map((func) => invokeRelations.updateStubStatus(
    functionId,
    undefined,
    func.toFuncMangledId,
    func.toFuncName,
    status
  )));
  await Promise.all(isDefineFuncs.map((func) => invokeRelations.updateStubStatus(
    functionId,
    func.toFuncId,
    undefined,
    undefined,
    status
  )));
}

async function getIntegrationFixedAddressExprs(functionId, versionId) {
  const fixedAddrsExprsMap = new Map();
  const allFileMap = new Map();
  const allFuncMap = new Map();
  const resFixedAddrsExprs = [];
  const [variables, invokeFuncs, allFiles, allFuncs] = await Promise.all([
    getFunctionVariables(functionId, true),
    invokeRelations.retrieveRelations(functionId, { sort: true, initRelations: true }),
    File.find({ versionId, kind: { $in: [fileModels.kind.file, fileModels.fileOutsideProject] } }, { fileName: 1, path: 1, _id: 1 }),
    Func.find({ versionId }, { _id: 1, functionName: 1 }),
  ]);
  allFiles.forEach((file) => {
    const fileId = file._id.toString();
    if (!allFileMap.has(fileId)) {
      allFileMap.set(fileId, file);
    }
  });
  allFuncs.forEach((func) => {
    const funcId = func._id.toString();
    if (!allFuncMap.has(funcId)) {
      allFuncMap.set(funcId, func);
    }
  });
  if (variables.fixedAddrs && variables.fixedAddrs.exprs) {
    Object.keys(variables.fixedAddrs.exprs).forEach((expr) => {
      let key = expr;
      if (!handleIntegrationVariables.checkAttachFileIdOrFunctionId(key)) {
        key += `@${functionId}`;
      }
      if (!fixedAddrsExprsMap.has(key)) {
        fixedAddrsExprsMap.set(key, variables.fixedAddrs.exprs[expr]);
      }
    });
  }
  const originalFunctionIds = [mongoose.Types.ObjectId(functionId)];
  invokeFuncs.forEach((func) => {
    if (func.toFuncId) {
      originalFunctionIds.push(mongoose.Types.ObjectId(func.toFuncId));
    }
  });
  const invokeFuncsVariables = await getFunctionOriginalVariables(originalFunctionIds);
  invokeFuncsVariables.forEach((funcVariable) => {
    const fileId = funcVariable.fileId.toString();
    const funcId = funcVariable.functionId.toString();
    const file = allFileMap.get(fileId);
    const func = allFuncMap.get(funcId);
    if (funcVariable.fixedAddrs && funcVariable.fixedAddrs.exprs) {
      Object.keys(funcVariable.fixedAddrs.exprs).forEach((exprName) => {
        let exprExist = false;
        const locations = [];
        const expr = funcVariable.fixedAddrs.exprs[exprName];
        expr.functionId = funcId;
        expr.functionName = func.functionName;
        if (expr.locations) {
          const newExprName = `${exprName}@${funcId}`;
          const fixedAddrsExprs = fixedAddrsExprsMap.get(newExprName);
          Object.keys(expr.locations).forEach((index) => {
            let status = false;
            if (fixedAddrsExprs
              && fixedAddrsExprs.enabled
              && fixedAddrsExprs.locations
              && fixedAddrsExprs.locations[index]) {
              status = true;
            }
            locations.push({
              index,
              status,
              ...expr.locations[index],
            });
          });
        }
        expr.locations = locations;
        resFixedAddrsExprs.forEach((resExpr) => {
          if (resExpr.exprName === exprName) {
            exprExist = true;
            let fileExist = false;
            resExpr.files.forEach((resFile) => {
              if (resFile.fileId === fileId) {
                fileExist = true;
                let funcExist = false;
                resFile.functions.forEach((func) => {
                  if (func.functionId === funcId) {
                    funcExist = true;
                  }
                });
                if (!funcExist) {
                  resFile.functions.push(expr);
                }
              }
            });
            if (!fileExist) {
              resExpr.files.push({
                fileId,
                fileName: file.fileName,
                filePath: file.path,
                functions: [expr],
              });
            }
          }
        });
        if (!exprExist) {
          resFixedAddrsExprs.push({
            exprName,
            files: [{
              fileId,
              fileName: file.fileName,
              filePath: file.path,
              functions: [expr],
            }],
          });
        }
      });
    }
  });
  return resFixedAddrsExprs;
}
async function saveIntegrationFixedAddressExprs(functionId, versionId, fixedAddrsExprs) {
  const allFuncMap = new Map();
  const newFixedAddrsExprs = {};
  const allFuncs = await Func.find({ versionId }, { _id: 1, functionName: 1 });
  allFuncs.forEach((func) => {
    const funcId = func._id.toString();
    if (!allFuncMap.has(funcId)) {
      allFuncMap.set(funcId, func);
    }
  });
  fixedAddrsExprs.forEach((expr) => {
    expr.functions.forEach((funcExpr) => {
      let { exprName } = expr;
      if (funcExpr.functionId !== functionId) {
        exprName += `@${funcExpr.functionId}`;
      }
      const locations = {};
      delete funcExpr.functionId;
      funcExpr.locations.forEach((location) => {
        const { index } = location;
        delete location.index;
        delete location.status;
        locations[index] = location;
      });
      funcExpr.locations = locations;
      funcExpr.enabled = 1;
      if (!newFixedAddrsExprs[exprName]) {
        delete funcExpr.functionName;
        newFixedAddrsExprs[exprName] = funcExpr;
      }
    });
  });
  const removeExprs = { $unset: {} };
  let removeExprsState = false;
  const testedFuncVariables = await getFunctionVariables(functionId, true);
  if (testedFuncVariables.fixedAddrs && testedFuncVariables.fixedAddrs.exprs) {
    const testedFuncExprs = testedFuncVariables.fixedAddrs.exprs;
    Object.keys(testedFuncExprs).forEach((exprName) => {
      const testCaseExpr = `data.fixedAddrs.exprs.${exprName}`;
      if (!newFixedAddrsExprs[exprName]) {
        removeExprs.$unset[testCaseExpr] = 1;
        removeExprsState = true;
      }
      if (handleIntegrationVariables.checkAttachFileIdOrFunctionId(exprName)
        && !allFuncMap.has(handleIntegrationVariables.getAttachFileIdOrFunctionId(exprName))) {
        removeExprs.$unset[testCaseExpr] = 1;
        removeExprsState = true;
      }
    });
  }
  // 更新被测函数绝对地址表达式
  await FunctionVariable.updateOne({ functionId: mongoose.Types.ObjectId(functionId) },
    { $set: { 'fixedAddrs.exprs': newFixedAddrsExprs } });
  // 清除测试用例中的绝对地址表达式
  if (removeExprsState) {
    await TestCase.updateMany({ functionId: mongoose.Types.ObjectId(functionId) }, removeExprs);
  }
  // 清除函数执行结果
  await removeResultOfExecution(functionId);
}
async function getIntegrationFixedAddress(functionId, fileId, versionId) {
  const allFuncMap = new Map();
  const allFileMap = await fileModels.getSourceFileByVersionId(versionId, null, { _id: 1, path: 1, fileName: 1 });
  const resFixedAddress = {
    exprs: [],
    bases: [],
  };
  const [allFuncs, variables] = await Promise.all([
    Func.find({ versionId }, { _id: 1, functionName: 1, fileId: 1 }),
    getFunctionVariables(functionId, true),
  ]);
  allFuncs.forEach((func) => {
    const funcId = func._id.toString();
    if (!allFuncMap.has(funcId)) {
      allFuncMap.set(funcId, func);
    }
  });
  if (variables.fixedAddrs && variables.fixedAddrs.exprs) {
    Object.keys(variables.fixedAddrs.exprs).forEach((key) => {
      const expr = variables.fixedAddrs.exprs[key];
      // expr存在且enabled才显示出来
      if (expr.functionId && allFuncMap.has(expr.functionId) && expr.enabled) {
        const funcId = expr.functionId;
        const func = allFuncMap.get(funcId);
        if (!func) {
          return;
        }
        const fileId = func.fileId.toString();
        const file = allFileMap.get(fileId);
        if (!file) {
          return;
        }
        resFixedAddress.exprs.push({
          name: key,
          functionId: funcId,
          functionName: func.functionName,
          fileId,
          filePath: file.path,
        });
      }
    });
  }
  if (variables.fixedAddrs && variables.fixedAddrs.bases) {
    Object.keys(variables.fixedAddrs.bases).forEach((key) => {
      const base = variables.fixedAddrs.bases[key];
      const file = base.fileId ? allFileMap.get(base.fileId) : allFileMap.get(fileId.toString());
      if (!file) {
        return;
      }
      resFixedAddress.bases.push({
        name: key,
        fileId: file._id.toString(),
        filePath: file.path,
      });
    });
  }
  return resFixedAddress;
}
/**
 * 集成测试初始化被测函数的绝对地址基地址
 * @param {String} functionId 被测函数Id
 * @param {String} versionId 版本id
 */
async function IntegrationAddInvokeFuncFixedAddressBases(funcVariables, sourceFileExts, versionType, allFileMap) {
  const { functionId, versionId, fileId } = funcVariables;
  if (versionType === projectVersions.versionType.unitTest) {
    return;
  }
  const testCases = await TestCase.find({ functionId });
  // 函数没有添加测试用例的时候才初始化
  if (testCases.length > 0) {
    return;
  }
  const newFixedAddressBases = {};
  const invokeFuncs = await invokeRelations.retrieveRelations(functionId, { sort: true, initRelations: true });
  if (!allFileMap) {
    allFileMap = new Map();
    const allFiles = await File.find({ versionId, kind: 1, extname: { $in: sourceFileExts } }, { _id: 1, path: 1 });
    allFiles.forEach((file) => {
      const fileId = file._id.toString();
      if (!allFileMap.has(fileId)) {
        allFileMap.set(fileId, file);
      }
    });
  }
  // 保留入口函数原始解析的绝对地址目标
  if (funcVariables.fixedAddrs && funcVariables.fixedAddrs.bases) {
    Object.keys(funcVariables.fixedAddrs.bases).forEach((baseName) => {
      const fixedAddrBasesFileId = funcVariables.fixedAddrs.bases[baseName].fileId;
      if (!fixedAddrBasesFileId || fixedAddrBasesFileId === fileId.toString()) {
        delete funcVariables.fixedAddrs.bases[baseName].fileId;
        delete funcVariables.fixedAddrs.bases[baseName].filePath;
        newFixedAddressBases[baseName] = funcVariables.fixedAddrs.bases[baseName];
      }
      if (fixedAddrBasesFileId && fixedAddrBasesFileId !== fileId.toString() && allFileMap.has(fixedAddrBasesFileId)) {
        newFixedAddressBases[baseName] = funcVariables.fixedAddrs.bases[baseName];
      }
    });
  }
  const invokeFunctionIds = [];
  invokeFuncs.forEach((func) => {
    if (func.toFuncId) {
      invokeFunctionIds.push(mongoose.Types.ObjectId(func.toFuncId));
    }
  });
  const invokeFuncsVariables = await getFunctionOriginalVariables(invokeFunctionIds);
  // 将新的参与集成函数的绝对地址base加进去
  invokeFuncsVariables.forEach((invokeFuncsVariable) => {
    const file = allFileMap.get(invokeFuncsVariable.fileId.toString());
    if (!file) {
      return;
    }
    if (invokeFuncsVariable.fixedAddrs && invokeFuncsVariable.fixedAddrs.bases) {
      Object.keys(invokeFuncsVariable.fixedAddrs.bases).forEach((baseName) => {
        newFixedAddressBases[baseName] = invokeFuncsVariable.fixedAddrs.bases[baseName];
        newFixedAddressBases[baseName].fileId = file._id.toString();
        newFixedAddressBases[baseName].filePath = file.path;
      });
    }
  });
  // 更新variable
  await FunctionVariable.updateOne({ functionId }, { $set: { 'fixedAddrs.bases': newFixedAddressBases } });
}
async function handleIntegrationStaticsOutput(functionId) {
  let inputStatics = {};
  const funcVariables = await getRelationFuncsVariables(functionId);
  if (funcVariables
    && funcVariables.variables
    && funcVariables.variables.statics) {
    inputStatics = funcVariables.variables.statics;
  }
  if (funcVariables
    && funcVariables.output
    && funcVariables.output.statics) {
    const variableDoc = {};
    const testCaseDoc = {};
    Object.keys(funcVariables.output.statics).forEach((staticName) => {
      if (!inputStatics[staticName]) {
        testCaseDoc[`data.output.statics.${staticName}`] = 1;
        staticName = handleIntegrationVariables.handleStaticsOutputFunctionId(functionId, staticName);
        variableDoc[`output.statics.${staticName}`] = 1;
      }
    });
    // 更新testcase和variable output
    await FunctionVariable.updateOne({ functionId }, { $unset: variableDoc });
    await TestCase.updateOne({ functionId }, { $unset: testCaseDoc });
  }
  return inputStatics;
}
/**
 * 通过mangledId获取函数
 * @param {ObjectId} versionId 版本Id
 * @param {String} mangledId 函数mangledId
 * @returns
 */
function getFunctionByMangledId(versionId, mangledId) {
  return Func.find({ versionId: mongoose.Types.ObjectId(versionId), mangledId });
}
function addFunctionPointerToInvokeFunction(cfg, funcsMap, filesMap, functionId, mangledIdToLocation) {
  const functionPointerList = [];
  if (cfg.callexpr) {
    const { callexpr } = cfg;
    Object.keys(callexpr).forEach((key) => {
      if (callexpr[key].mangled) {
        if (callexpr[key].begin) {
          mangledIdToLocation.set(callexpr[key].mangled, callexpr[key].begin);
        }
        return;
      }
      const fpName = `fp_${key}`;
      const location = callexpr[key].begin && callexpr[key].end ? { begin: callexpr[key].begin, end: callexpr[key].end } : {};
      const file = funcsMap.has(functionId) ? filesMap.get(funcsMap.get(functionId).fileId.toString()) : null;
      let coveredCaseIds = [];
      if (cfg.invokes
        && cfg.invokes[functionId].callexpr
        && cfg.invokes[functionId].callexpr[key]
        && cfg.invokes[functionId].callexpr[key].testIds) {
        coveredCaseIds = coveredCaseIds.concat(Object.keys(cfg.invokes[functionId].callexpr[key].testIds));
      }
      if (callexpr[key].begin) {
        mangledIdToLocation.set(fpName, callexpr[key].begin);
      }
      functionPointerList.push({
        coveredCaseIds,
        enableStub: false,
        end: true,
        fileDesc: 'funcPointer',
        fileId: file ? file._id.toString() : '',
        filePath: file ? file.path : '',
        functionId: '',
        functionName: fpName,
        functionType: 'funcPointer',
        insideProject: true,
        jumpable: Object.keys(location).length > 0,
        mangledId: fpName,
        location,
        callexprId: key,
      });
    });
  }
  return functionPointerList;
}
/**
 * 递归处理函数调用层级深度
 * @param {String} functionId 当前函数ID
 * @param {String} callChain 函数调用链
 * @param {Array} InvokeChildrens 子函数
 * @param {Array[Object]} invokeFuncs 函数调用关系
 * @param {Map} funcsMap 版本下可测试函数
 * @param {Map} filesMap 版本下可测试文件
 * @param {Number} invokeFuncDepth 获取调用函数深度
 */
async function handleInvokeRecursion(
  functionId,
  callChain,
  InvokeChildrens = [],
  invokeFuncs,
  funcsMap,
  filesMap,
  invokeFuncDepth = 1
) {
  const nextInvokeFuncs = [];
  invokeFuncs.forEach((item) => {
    if (item.fromFuncId.toString() === functionId.toString()) {
      nextInvokeFuncs.push(item);
    }
  });
  await utility.arrayChunkOperation(nextInvokeFuncs, 10, async (func) => {
    let { toFuncId } = func;
    const {
      fromFuncFileId,
      fromFuncId,
      toFuncMangledId,
      toFuncFileId,
      toFuncName,
      toFuncPointer,
      toFuncCtor,
      enableStub,
      testCases,
    } = func;
    let jumpable = false;
    let insideProject = true;
    let end = true;
    let filePath;
    let functionType;
    let fileDesc = 'invoke';
    // 移除函数指针
    if (toFuncPointer === true || toFuncCtor === true) {
      return;
    }
    // 移除参与集成函数中手动创建的桩函数
    if (!toFuncId) {
      const fromFuncVariables = await getFunctionVariables(fromFuncId);
      const stubs = fromFuncVariables.stubs || {};
      // c++stubs中使用MangledId做key，c中使用functionName做key
      const stub = stubs[toFuncName] || stubs[toFuncMangledId];
      if (stub && stub.stubsTriggerKind && stub.stubsTriggerKind === tests.stubTriggerKind.manual) {
        return;
      }
    }
    toFuncId = toFuncId ? toFuncId.toString() : '';
    const isDefine = funcsMap.has(toFuncId) && funcsMap.get(toFuncId).fileId.toString() === toFuncFileId.toString();
    // 未定义函数同时未选择文件时使用来源函数所在文件
    const fileId = toFuncFileId ? toFuncFileId.toString() : fromFuncFileId.toString();
    const toFuncFile = filesMap.get(fileId);
    if (toFuncFile) {
      filePath = toFuncFile.path;
      // 是否项目外函数又当前选择文件确定
      insideProject = toFuncFile.kind !== fileModels.kind.fileOutsideProject;
    }
    // 项目外函数过滤
    if (!insideProject) {
      return;
    }
    // 函数是否已定义
    if (isDefine) {
      const invokeFunc = funcsMap.get(toFuncId);
      functionType = invokeFunc && invokeFunc.kind === fileElement.elementKind.specialFunction ? 'special' : 'defined';
    } else {
      functionType = 'unDefined';
      // 系统函数
      if (!enableStub) {
        filePath = '-';
        functionType = 'sysFunc';
      }
      // 手动创建函数
      if (toFuncName === toFuncMangledId) {
        functionType = 'manual';
      }
    }
    // 函数是否能跳转到文件位置
    if (toFuncId
      && funcsMap.get(toFuncId)
      && funcsMap.get(toFuncId).fileId.toString() === fileId) {
      jumpable = insideProject;
      fileDesc = 'define';
    }
    if (toFuncId
      && !enableStub
      && (invokeFuncs.filter((invokeFunc) => invokeFunc.fromFuncId.toString() === toFuncId.toString())).length > 0) {
      end = false;
    }
    InvokeChildrens.push({
      functionId: toFuncId,
      functionName: toFuncId && funcsMap.has(toFuncId) ? funcsMap.get(toFuncId).functionFullName : toFuncName,
      fileId,
      location: toFuncId && funcsMap.get(toFuncId) ? funcsMap.get(toFuncId).location : {},
      coveredCaseIds: testCases ? utility.arrayDeduplicate(testCases) : [],
      filePath,
      enableStub,
      insideProject,
      functionType,
      end,
      jumpable,
      fileDesc,
      mangledId: toFuncMangledId,
      callChain: `${callChain}:${toFuncMangledId}`,
    });
  });
  /* TODO： 函数cfg优化，将invokes中callexpr的testId合并到外层的callexpr中 */
  const cfg = await getFunctionCFG(functionId);
  // 根据出现位置对函数和指针进行排序
  const mangledIdToLocation = new Map();
  const funcPointerList = addFunctionPointerToInvokeFunction(cfg, funcsMap, filesMap, functionId, mangledIdToLocation);
  InvokeChildrens.push(...funcPointerList);
  InvokeChildrens.sort((a, b) => {
    if (!mangledIdToLocation.has(a.mangledId)) {
      return 1;
    }
    if (!mangledIdToLocation.has(b.mangledId)) {
      return -1;
    }
    const aLocation = mangledIdToLocation.get(a.mangledId);
    const bLocation = mangledIdToLocation.get(b.mangledId);
    return aLocation.line === bLocation.line ? aLocation.column - bLocation.column : aLocation.line - bLocation.line;
  });
  await utility.arrayChunkOperation(InvokeChildrens, 10, async (nextFunc) => {
    const funcCallChainArray = nextFunc.callChain ? nextFunc.callChain.split(':') : [];
    if (nextFunc.end || funcCallChainArray.length === invokeFuncDepth + 1) {
      return;
    }
    funcCallChainArray.pop();
    if (funcCallChainArray.includes(nextFunc.mangledId)) {
      return;
    }
    nextFunc.children = [];
    await handleInvokeRecursion(nextFunc.functionId, nextFunc.callChain, nextFunc.children, invokeFuncs, funcsMap, filesMap, invokeFuncDepth);
  });
}
/**
 * 获取函数调用关系下一层数据
 * @param {String|ObjectId} functionId 被测函数id
 * @param {String|ObjectId} currentFunctionId  当前节点函数id
 * @param {String|ObjectId} versionId 版本id
 * @param {Number} invokeFuncDepth 获取被调用函数的层级数,0：当前函数信息，注意深度最好不要超过3
 * @param {Boolean} includeCurrentFunc 包含当前函数
 * @returns {Array|Object} Array
 * 函数类型：
 * entryFunc:入口函数
 * system:系统函数,
 * defined :已定义函数，
 * unDefined:未定义函数,
 * manual:手动创建桩函数,
 * special:模版特化函数,
 * funcPointer:函数指针,
 */
async function getInvokeChildren(functionId, currentFunctionId, versionId, invokeFuncDepth = 0, includeCurrentFunc = false) {
  functionId = functionId.toString();
  const invokeFuncs = await invokeRelations.retrieveRelations(functionId, { initRelations: true, includeCtor: false });
  const [currentFunc] = await Func.aggregate([
    {
      $match: {
        _id: mongoose.Types.ObjectId(currentFunctionId || functionId),
      },
    },
    {
      $lookup: {
        from: 'files',
        localField: 'fileId',
        foreignField: '_id',
        as: 'file',
      },
    },
    {
      $unwind: '$file',
    },
    {
      $project: {
        functionId: '$_id',
        functionName: '$functionFullName',
        kind: '$kind',
        fileId: '$fileId',
        filePath: '$file.path',
        location: '$location',
        coveredCaseIds: [],
        mangledId: 1,
        _id: 0,
      },
    },
  ]);
  if (includeCurrentFunc) {
    invokeFuncs.forEach((func) => {
      if (func.testCases && func.testCases.length > 0) {
        currentFunc.coveredCaseIds = utility.arrayDeduplicate(currentFunc.coveredCaseIds.concat(func.testCases.map((id) => id.toString())));
      }
    });
    currentFunc.enableStub = false;
    currentFunc.functionType = currentFunctionId === functionId ? 'entryFunc' : 'defined';
    currentFunc.insideProject = true;
    currentFunc.end = invokeFuncs.length === 0;
    currentFunc.jumpable = true;
    currentFunc.fileDesc = 'define';
    if (currentFunc.kind === fileElement.elementKind.specialFunction) {
      currentFunc.functionType = 'special';
    }
  }
  // 只要当前函数信息
  if (invokeFuncDepth === 0) {
    return [currentFunc];
  }
  const funcsMap = new Map();
  const allFuncs = await Func.find({
    versionId,
    kind: { $in: fileElement.getCountableKinds() },
  }, {
    _id: 1, location: 1, kind: 1, fileId: 1, functionFullName: 1,
  }).lean();
  allFuncs.forEach((func) => {
    funcsMap.set(func._id.toString(), func);
  });
  const filesMap = new Map();
  const allFiles = await File.find(
    {
      versionId,
      kind: { $in: [fileModels.kind.file, fileModels.kind.fileOutsideProject] },
    },
    { _id: 1, path: 1, kind: 1 }
  ).lean();
  allFiles.forEach((file) => {
    filesMap.set(file._id.toString(), file);
  });

  let resFunctions = [];
  await handleInvokeRecursion(
    currentFunc.functionId.toString(),
    currentFunc.mangledId,
    resFunctions,
    invokeFuncs,
    funcsMap,
    filesMap,
    invokeFuncDepth
  );
  if (includeCurrentFunc) {
    resFunctions = [{ ...currentFunc, children: resFunctions }];
  }
  return resFunctions;
}
/**
 *  获取函数调用边调用次数
 * @param {Array[String]} preFuncIds 前置节点Id
 * @param {Array[String]} testIds 测试用例Id
 * @param {String} toFuncFileId
 * @param {String} toFuncMangledId
 * @param {String} callexprId 函数指针需要使用callexprId过滤数据
 * @returns
 */
async function callExprTimes(preFuncIds, testIds, toFuncFileId, toFuncMangledId, callexprId) {
  if (!preFuncIds || !preFuncIds.length) {
    throw new Error('need pre nodes');
  }
  const fromFuncId = preFuncIds[preFuncIds.length - 1];
  const funcId = preFuncIds[0]; // 入口函数Id
  const query = {
    functionId: funcId,
    preFuncIds,
    $or: [{ 'callFuncs.toFuncMangledId': toFuncMangledId }, { 'callFuncs.callexprs.callexprId': callexprId }],
  };
  if (testIds) {
    query.testcaseId = { $in: testIds };
  }
  const testInvokes = await TestInvoke.find(query);
  let callExprIds = callexprId ? [callexprId] : [];
  testInvokes.forEach((testInvoke) => {
    // 过滤出需要的边
    testInvoke.callFuncs = testInvoke.callFuncs.filter((item) => item.toFuncMangledId === toFuncMangledId
      || (callexprId && item.callexprs.filter((expr) => expr.callexprId === callexprId).length > 0));
    testInvoke.callFuncs.forEach((callFunc) => {
      callFunc.callexprs.forEach((callExpr) => {
        callExprIds.push(callExpr.callexprId);
      });
    });
  });

  const cfg = await FunctionCFG.findOne({
    functionId: fromFuncId,
  });
  const { fileId } = cfg;
  const file = await File.findOne({ _id: fileId });
  callExprIds = new Set(callExprIds);
  const callExprs = cfg.callexpr.filter((item) => item.mangled === toFuncMangledId
    || callExprIds.has(item.id.toString())) // 特殊情况：函数指针调用cfg里面的callexpr没有mangleId，所以根据执行结果的callExprId来获取
    .map((item) => ({
      location: {
        begin: item.begin,
        end: item.end,
      },
      id: item.id.toString(),
      mangled: toFuncMangledId,
      times: 0,
    }));
  let totalTimes = 0;
  callExprs.forEach((item) => {
    testInvokes.forEach((invoke) => {
      invoke.callFuncs.forEach((callFunc) => {
        callFunc.callexprs.forEach((callexpr) => {
          if (callexpr.callexprId.toString() === item.id) {
            item.times += callexpr.times;
            totalTimes += callexpr.times;
          }
        });
      });
    });
  });
  return {
    callExprs,
    totalTimes,
    filePath: file.path,
  };
}
/**
 * 替换掉特化函数中的文件为模板原型文件
 * @param {Object} invokeFunc 集成函数
 * @returns func Object
 */
async function handleSpecializationFile(invokeFunc) {
  invokeFunc.isSpecialization = false;
  if (!invokeFunc.functionId) {
    return;
  }
  const { kind, rawTemplateId } = await Func.findOne(
    { _id: mongoose.Types.ObjectId(invokeFunc.functionId) },
    { kind: 1, rawTemplateId: 1 }
  );
  if (kind !== fileElement.elementKind.specialFunction || !rawTemplateId) {
    return;
  }

  const [templateFunc] = await Func.aggregate([
    {
      $match: {
        _id: rawTemplateId,
      },
    },
    {
      $lookup: {
        from: 'files',
        localField: 'fileId',
        foreignField: '_id',
        as: 'file',
      },
    },
    {
      $unwind: '$file',
    },
    {
      $project: {
        fileId: '$fileId',
        functionName: 1,
        fileName: '$file.fileName',
        path: '$file.path',
      },
    },
  ]);
  if (!templateFunc) {
    return;
  }
  const {
    fileId, fileName, path, functionName,
  } = templateFunc;
  invokeFunc.fileId = fileId;
  invokeFunc.fileName = fileName;
  invokeFunc.functionName = functionName;
  invokeFunc.filePath = path;
  invokeFunc.files = [{ fileId, fileName, path }];
  invokeFunc.isSpecialization = true;
  invokeFunc.enableStub = false;
}

async function getInvokeCoveredLocations(functionId, invokeChain) {
  if (!utility.isArrayType(invokeChain) || invokeChain.length === 0) {
    return [];
  }
  const lastInvokeFuncId = invokeChain[invokeChain.length - 1];
  const [functionCfg, invokeFuncCfg] = await Promise.all([
    getFunctionCFG(functionId),
    getFunctionCFG(lastInvokeFuncId, functionId),
  ]);
  if (!functionCfg || !invokeFuncCfg || !functionCfg.invokes) {
    return [];
  }
  const { invokes } = functionCfg;
  const locations = [];
  Object.keys(invokes).forEach((key) => {
    if (key !== lastInvokeFuncId.toString()) {
      return;
    }
    if (!invokes[key].callexpr) {
      return;
    }
    Object.keys(invokes[key].callexpr).forEach((exprId) => {
      if (!invokes[key].callexpr[exprId].testIds || Object.keys(invokes[key].callexpr[exprId].testIds).length === 0) {
        return;
      }
      if (!invokeFuncCfg.callexpr || !invokeFuncCfg.callexpr[exprId]) {
        return;
      }
      locations.push(
        {
          begin: invokeFuncCfg.callexpr[exprId].begin,
          end: invokeFuncCfg.callexpr[exprId].end,
          testIds: invokes[key].callexpr[exprId].testIds,
        }
      );
    });
  });
  const result = getCFGJson(invokeFuncCfg);
  result.invokes = locations;
  return result;
}

async function addStubParamsOutput(functionId, stubName) {
  const doc = { $set: {} };
  doc.$set[`output.stubs.${stubName}`] = {};
  await FunctionVariable.updateOne({ functionId: mongoose.Types.ObjectId(functionId) }, doc);
}

async function removeStubParamsOutput(functionId, stubName) {
  const doc = { func: { $unset: {} }, testCase: { $unset: {} } };
  doc.func.$unset[`output.stubs.${stubName}`] = 1;
  doc.testCase.$unset[`data.output.stubs.${stubName}`] = 1;
  await FunctionVariable.updateOne({ functionId: mongoose.Types.ObjectId(functionId) }, doc.func);
  await TestCase.updateMany({ functionId: mongoose.Types.ObjectId(functionId) }, doc.testCase);
}

async function replaceFanInAndFanOut(versionId) {
  const map = new Map();
  const list = await FunctionVariable.aggregate([
    { $match: { versionId: mongoose.Types.ObjectId(versionId) } },
    {
      $lookup: {
        from: 'functions', localField: 'functionId', foreignField: '_id', as: 'func',
      },
    },
    { $unwind: '$func' },
    {
      $addFields: {
        stubs: { $objectToArray: '$stubs' },
      },
    },
    {
      $addFields: {
        stubs: {
          $filter: {
            input: '$stubs',
            as: 'stub',
            cond: { $eq: ['$$stub.v.@isCall', 1] },
          },
        },
      },
    },
    {
      $project: {
        _id: 1,
        stubs: '$stubs.v.@mangled',
        mangledId: '$func.mangledId',
        functionId: 1,
      },
    },
  ]);
  list.forEach((doc) => {
    const { stubs } = doc;
    doc.fanIn = 0;
    doc.fanOut = stubs && stubs.length ? stubs.length : 0;
    const k = doc.mangledId;
    map.set(k, map.get(k) || []);
    map.get(k).push(doc);
  });
  list.forEach((doc) => {
    const { stubs } = doc;
    if (stubs && stubs.length) {
      stubs.forEach((item) => {
        const arr = map.get(item);
        if (arr && arr.length) {
          arr.forEach((item1) => {
            item1.fanIn += 1;
          });
        }
      });
    }
  });
  const batchSize = 100;
  for (let i = 0; i < list.length; i += batchSize) {
    const batch = list.slice(i, i + batchSize);
    const arr = batch.map((item) => ({
      updateOne: {
        filter: { _id: item.functionId },
        update: {
          $set: {
            fanIn: item.fanIn,
            fanOut: item.fanOut,
          },
        },
      },
    }));
    // eslint-disable-next-line no-await-in-loop
    await Func.bulkWrite(arr);
  }
}
/**
 * 根据functionVariables获取版本内所有扇入扇出函数
 * @param {String} versionId 版本id
 * @returns {Promise<Array<Object>} ret
 */
async function getAllFanInAndFanOut(versionId) {
  const map = new Map();
  const list = await FunctionVariable.aggregate([
    { $match: { versionId: mongoose.Types.ObjectId(versionId) } },
    {
      $lookup: {
        from: 'functions', localField: 'functionId', foreignField: '_id', as: 'func',
      },
    },
    { $unwind: '$func' },
    {
      $addFields: {
        stubs: { $objectToArray: '$stubs' },
      },
    },
    {
      $addFields: {
        stubs: {
          $filter: {
            input: '$stubs',
            as: 'stub',
            cond: { $eq: ['$$stub.v.@isCall', 1] },
          },
        },
      },
    },
    {
      $project: {
        _id: 1,
        stubs: '$stubs.v.@mangled',
        invokeFuncs: '$stubs.v.@name',
        mangledId: '$func.mangledId',
        functionId: 1,
        functionName: '$func.functionName',
        fileId: '$func.fileId',
      },
    },
  ]);
  list.forEach((doc) => {
    const { stubs } = doc;
    doc.fanIn = 0;
    doc.fanOut = stubs && stubs.length ? stubs.length : 0;
    doc.beInvokeFuncs = [];
    doc.invokeFuncs = doc.invokeFuncs ? doc.invokeFuncs.map((funName) => ({ name: funName })) : [];
    const k = doc.mangledId;
    map.set(k, map.get(k) || []);
    map.get(k).push(doc);
  });
  list.forEach((doc) => {
    const { stubs } = doc;
    if (stubs && stubs.length) {
      stubs.forEach((item) => {
        const arr = map.get(item);
        if (arr && arr.length) {
          arr.forEach((item1) => {
            item1.fanIn += 1;
            item1.beInvokeFuncs.push({
              name: doc.functionName,
            });
          });
        }
      });
    }
  });
  return list;
}
/**
 * 获取集成函数
 * @param {String|ObjectId} functionId 入口函数id
 * @param {String} functionType 函数类型 定义函数：defined
 * @param {Boolean} funcEnableStub 打桩状态 true：打桩
 */
async function getIntegratedFunctions(functionId, functionType, funcEnableStub = false) {
  const checkOutputFuncs = [];
  const integratedFuncs = await invokeRelations.retrieveRelations(functionId, { sort: true, initRelations: true });
  const funcVariable = await FunctionVariable.findOne({ functionId: mongoose.Types.ObjectId(functionId) },
    { versionId: 1, integratedFuncs: 1 }).lean();
  if (!funcVariable) {
    return checkOutputFuncs;
  }
  const fileIds = new Set();
  const functionIds = new Set();
  integratedFuncs.forEach((func) => {
    if (func.toFuncId) {
      functionIds.add(func.toFuncId.toString());
    }
    if (func.toFuncFileId) {
      fileIds.add(func.toFuncFileId.toString());
    }
  });
  const funcsMap = new Map();
  const funcs = await Func.find({ _id: { $in: Array.from(functionIds) } }, {
    _id: 1, functionName: 1, fileId: 1, mangledId: 1,
  }).lean();
  funcs.forEach((func) => {
    funcsMap.set(func._id.toString(), func);
  });
  const filesMap = new Map();
  const files = await File.find({ _id: { $in: Array.from(fileIds) } }, { _id: 1, fileName: 1, path: 1 }).lean();
  files.forEach((file) => {
    filesMap.set(file._id.toString(), file);
  });
  const topIntegratedFuncs = [];
  const addedMangledId = [];
  const variableIntegratedFuncs = funcVariable.integratedFuncs || {};
  integratedFuncs.forEach(({
    toFuncId,
    toFuncMangledId,
    toFuncFileId,
    enableStub,
  }) => {
    switch (functionType) {
      case 'defined': {
        if (funcEnableStub !== enableStub
          || !toFuncId
          || !funcsMap.has(toFuncId.toString())
          || !filesMap.has(toFuncFileId.toString())) {
          break;
        }
        const func = funcsMap.get(toFuncId.toString());
        const file = filesMap.get(toFuncFileId.toString());
        const obj = {
          functionName: func.functionName,
          functionId: func._id,
          mangledId: func.mangledId,
          fileId: file._id,
          filePath: file.path,
          checkOutput: false,
        };
        if (addedMangledId.includes(toFuncMangledId)) {
          break;
        }
        if (variableIntegratedFuncs[toFuncMangledId]) {
          topIntegratedFuncs.push({ ...obj, checkOutput: true });
        } else {
          checkOutputFuncs.push(obj);
        }
        addedMangledId.push(toFuncMangledId);
        break;
      }
      default: break;
    }
  });
  // 将已添加的集成函数排序到前面
  if (topIntegratedFuncs.length > 0) {
    checkOutputFuncs.unshift(...topIntegratedFuncs);
  }
  return checkOutputFuncs;
}
/**
 * 更新检查项集成函数
 * @param {string|ObjectId} functionId 入口函数id
 * @param {Array[string]} mangledIds 需要添加的集成函数的mangledId
 */
async function updateIntegratedFunctions(functionId, mangledIds) {
  const funcVariable = await FunctionVariable.findOne({ functionId: mongoose.Types.ObjectId(functionId) },
    { versionId: 1, integratedFuncs: 1, 'output.integratedFuncs': 1 }).lean();
  if (!funcVariable) {
    throw new Error('function variable not found');
  }
  const vdoc = { $unset: {}, $set: {} };
  const tdoc = { $unset: {}, $set: {} };
  // 移除旧的集成函数
  if (funcVariable.integratedFuncs) {
    Object.keys(funcVariable.integratedFuncs).forEach((mangledId) => {
      if (mangledIds.includes(mangledId)) {
        return;
      }
      vdoc.$unset[`integratedFuncs.${mangledId}`] = 1;
      vdoc.$unset[`output.integratedFuncs.${mangledId}`] = 1;
      tdoc.$unset[`data.integratedFuncs.${mangledId}`] = 1;
      tdoc.$unset[`data.output.integratedFuncs.${mangledId}`] = 1;
    });
  }
  // 添加新的集成函数
  funcVariable.integratedFuncs = funcVariable.integratedFuncs || {};
  mangledIds.forEach((mangledId) => {
    if (funcVariable.integratedFuncs[mangledId]) {
      return;
    }
    vdoc.$set[`integratedFuncs.${mangledId}`] = {};
  });
  return Promise.all([
    FunctionVariable.updateOne({ functionId: mongoose.Types.ObjectId(functionId) }, vdoc),
    TestCase.updateMany({ functionId: mongoose.Types.ObjectId(functionId) }, tdoc),
  ]);
}
/**
 * 更新自定义全局变量
 * @param {string|ObjectId} functionId 被测函数id
 * @param {Array[Object]} globals 自定义的全局变量
 */
async function updateSelfDefinedGlobals(functionId, globals = []) {
  const functionVariable = await FunctionVariable.findOne({ functionId }, {
    variables: 1, malloc: 1, stubs: 1, output: 1,
  });
  if (!functionVariable) {
    throw new PhoenixError('not found', 'function variables not found');
  }
  const testCases = await TestCase.find({ functionId }, { 'data.stubs': 1 });
  const originalGlobals = functionVariable.variables && functionVariable.variables.global ? functionVariable.variables.global : {};
  const originalMalloc = functionVariable.malloc || {};
  const originalOutputGlobal = functionVariable.output && functionVariable.output.global ? functionVariable.output.global : {};
  const doc = {
    func: { $set: {}, $unset: {} },
    case: { $set: {}, $unset: {} },
  };
  let removeGlobalNames = new Set();
  // 处理新增和更新
  globals.forEach(({ globalName, typeName, arrayLength }) => {
    const illegal = utility.checkIllegalName(globalName);
    if (illegal) {
      throw new PhoenixError('invalid request', illegal);
    }
    if ((originalGlobals[globalName] && !originalGlobals[globalName].selfDefined) || originalMalloc[globalName]) {
      throw new PhoenixError('name already used', `global name:${globalName} already used`);
    }
    const newObj = { '@type': typeName, selfDefined: true };
    if (arrayLength) {
      newObj['@length'] = arrayLength;
      newObj['@elementType'] = typeName;
      newObj['@type'] = `${typeName} [${arrayLength}]`;
    }
    doc.func.$set[`variables.global.${globalName}`] = newObj;
    if (originalOutputGlobal[globalName]) {
      doc.func.$set[`output.global.${globalName}`] = newObj;
    }
    // 新旧数据类型不一致需要清除测试用例中的值
    if (originalGlobals[globalName]) {
      let updateTestcase = false;
      const originalGlobalType = originalGlobals[globalName]['@length'] ? originalGlobals[globalName]['@elementType'] : originalGlobals[globalName]['@type'];
      if (originalGlobalType !== typeName) {
        updateTestcase = true;
      } else if (arrayLength !== originalGlobals[globalName]['@length']) {
        updateTestcase = true;
      }
      if (updateTestcase) {
        doc.case.$unset[`data.variables.global.${globalName}`] = 1;
        doc.case.$unset[`data.output.global.${globalName}`] = 1;
        removeGlobalNames.add(globalName);
      }
    }
  });
  // 处理删除
  Object.keys(originalGlobals).forEach((key) => {
    const originalGlobal = originalGlobals[key];
    // 忽略非手动添加全局变量
    if (!originalGlobal.selfDefined) {
      return;
    }
    const selfDefinedGlobal = globals.filter((item) => item.globalName === key);
    if (selfDefinedGlobal.length > 0) {
      return;
    }
    // 清除variable中的全局变量
    doc.func.$unset[`variables.global.${key}`] = 1;
    doc.func.$unset[`output.global.${key}`] = 1;
    // 清除测试用例中的全局变量
    doc.case.$unset[`data.variables.global.${key}`] = 1;
    doc.case.$unset[`data.output.global.${key}`] = 1;
    removeGlobalNames.add(key);
  });
  // 清除测试用例中桩函数下的的全局变量
  if (removeGlobalNames.size > 0 && testCases.length > 0) {
    removeGlobalNames = Array.from(removeGlobalNames);
    testCases.forEach((testCase) => {
      if (testCase.data && testCase.data.stubs) {
        const { stubs } = testCase.data;
        Object.keys(stubs).forEach((stubName) => {
          const times = stubs[stubName].times || 1;
          if (stubs[stubName]['@value']) {
            for (let i = 0; i < times; i += 1) {
              for (let j = 0; j < removeGlobalNames.length; j += 1) {
                const globalName = removeGlobalNames[j];
                doc.case.$unset[`data.stubs.${stubName}.@value.${i}.global.${globalName}`] = 1;
              }
            }
          }
        });
      }
    });
  }
  await FunctionVariable.updateOne({ functionId }, doc.func);
  await TestCase.updateMany({ functionId }, doc.case);
  await removeResultOfExecution(functionId);
}
/**
 * 获取函数自定义全局变量
 * @param {string} functionId 被测函数id
 */
async function getSelfDefinedGlobals(functionId) {
  const { variables } = await FunctionVariable.findOne({ functionId }, { variables: 1 });
  if (!variables) {
    throw new PhoenixError('not found', 'function variables not found');
  }
  const selfDefinedGlobals = [];
  if (!variables.global) {
    return selfDefinedGlobals;
  }
  Object.keys(variables.global).forEach((globalName) => {
    const globals = variables.global;
    if (!globals[globalName].selfDefined) {
      return;
    }
    const obj = {
      globalName,
      typeName: globals[globalName]['@type'],
    };
    if (globals[globalName]['@length']) {
      obj.typeName = globals[globalName]['@elementType'];
      obj.arrayLength = globals[globalName]['@length'];
    }
    selfDefinedGlobals.push(obj);
  });
  return selfDefinedGlobals;
}

function mergeCfgCovered(funcCfg, coveredFiles, testId = '1') {
  Object.keys(coveredFiles).forEach((key) => {
    utility.setValueByKeyPath(funcCfg, `${utility.replaceAll(key, '|', '.')}.testIds.${testId}`, coveredFiles[key]);
  });
  return funcCfg;
}

function mergeDecisionCasesTestIds(funcCfg) {
  if (!funcCfg.decisions) {
    return;
  }
  Object.keys(funcCfg.decisions).forEach((decisionId) => {
    const decision = funcCfg.decisions[decisionId];
    if (!decision.coveredConditions) {
      return;
    }
    if (!decision.cases) {
      return;
    }
    const casesMap = {};
    Object.keys(decision.cases).forEach((caseId) => {
      casesMap[decision.cases[caseId].condKey] = caseId;
    });
    Object.keys(decision.coveredConditions).forEach((condKey) => {
      if (!casesMap[condKey]) {
        return;
      }
      if (!decision.cases[casesMap[condKey]].testIds) {
        decision.cases[casesMap[condKey]].testIds = {};
      }
      Object.keys(decision.coveredConditions[condKey].testIds).forEach((testId) => {
        if (decision.cases[casesMap[condKey]].testIds[testId] === undefined) {
          decision.cases[casesMap[condKey]].testIds[testId] = 0;
        }
        decision.cases[casesMap[condKey]].testIds[testId] += decision.coveredConditions[condKey].testIds[testId];
      });
    });
  });
}

function completeDecisionCases(funcCfg, mcdcKind) {
  if (!funcCfg.decisions) {
    return funcCfg;
  }
  Object.keys(funcCfg.decisions).forEach((decisionId) => {
    const conditions = [];
    if (funcCfg.decisions[decisionId].conditionsInfo) {
      Object.keys(funcCfg.decisions[decisionId].conditionsInfo).forEach((conditionId) => {
        conditions.push({ ...funcCfg.decisions[decisionId].conditionsInfo[conditionId], id: conditionId });
      });
    }
    const { cases, conditionCoveredPairs } = getMcdcCases(
      conditions,
      funcCfg.decisions[decisionId].coveredConditions || {},
      mcdcKind,
      true
    );
    funcCfg.decisions[decisionId].cases = cases;
    funcCfg.decisions[decisionId].conditionCoveredPairs = conditionCoveredPairs;
  });
  return funcCfg;
}

/**
 *  更新函数注释
 * @param {string|ObjectId} functionId  函数id
 * @param {string} versionType 版本类型
 * @param {Array[Object]} fileFuncsLocation 当前函数所在文件的所有函数行数信息
 * @param {Array[Object]} userDefinedField 函数自定义字段
 */
async function updateFunctionComment(func, fileFuncsLocation, userDefinedField) {
  if (!userDefinedField) {
    return;
  }
  if (!func) {
    return;
  }
  const funcUserDefinedFields = func.userDefinedFields ? func.userDefinedFields : [];
  let funcCommentData = '';
  const endLine = func.location.line;
  let beginLine = 1;
  if (endLine !== 1 && fileFuncsLocation.length > 1) {
    fileFuncsLocation.forEach((item, index) => {
      // 函数上注释的行数范围
      if (item === func.location.line && index > 0) {
        beginLine = fileFuncsLocation[index - 1];
      }
    });
  }
  const commentData = await FileComment.find({
    fileId: func.fileId,
    type: fileModels.fileCommentType.funcAbove,
    line: { $gt: beginLine, $lt: endLine },
  }, ['line', 'comment']);
  if (commentData.length > 0) {
    funcCommentData = commentData[commentData.length - 1].comment;
  }
  let exist = false;
  funcUserDefinedFields.forEach((item) => {
    if (item.key.toString() === userDefinedField._id.toString()) {
      item.value = funcCommentData;
      exist = true;
    }
  });
  if (!exist) {
    funcUserDefinedFields.push({ name: userDefinedField.name, key: userDefinedField._id, value: funcCommentData });
  }
  await Func.updateOne({ _id: func._id }, { $set: { userDefinedFields: funcUserDefinedFields } });
}
function updateFuncBorderVariable(func, variable) {
  const doc = {
    $set: {
      versionId: func.versionId,
      fileId: func.fileId,
      functionId: func._id,
      ...variable,
    },
  };
  return FuncBorderVariable.findOneAndUpdate({ functionId: func._id }, doc, { upsert: true });
}
function getFuncBorderVariable(functionId) {
  return FuncBorderVariable.findOne({ functionId });
}
/**
 *  获取函数测试用例累加覆盖率
 * @param {String|ObjectId} functionId 函数id
 * @param {Array[String]} testcaseIds 测试用例
 * @param {Number} mcdcKind mcdc算法
 * @param {Boolean} mergeCoverage 是否合并测试用例覆盖率
 * @returns
 */
async function getTestcasesCoverage(functionId, testcaseIds, mcdcKind = tests.mcdcKind.masking, mergeCoverage = false) {
  if (utility.isEmpty(testcaseIds)) {
    return [];
  }
  // 获取原始的cfg数据
  let funcCfg = await getFunctionCFG(functionId, null, false);
  ['nodes', 'edges', 'branches', 'decisions'].forEach((key) => {
    if (!funcCfg[key]) {
      funcCfg[key] = {};
    }
  });
  // 获取测试用例覆盖数据
  const testcasesCovered = await TestCaseCoveredField.find({
    functionId: mongoose.Types.ObjectId(functionId),
    testcaseId: { $in: testcaseIds.map((id) => mongoose.Types.ObjectId(id)) },
  });
  if (utility.isEmpty(testcasesCovered)) {
    return [];
  }
  const testcaseCoveredMap = {};
  testcasesCovered.forEach((coveredFields) => {
    testcaseCoveredMap[coveredFields.testcaseId.toString()] = coveredFields.coveredFields;
  });
  const result = [];
  testcaseIds.forEach((testcaseId) => {
    if (!testcaseCoveredMap[testcaseId]) {
      return;
    }
    // 循环合并测试用例覆盖数据
    funcCfg = mergeCfgCovered(funcCfg, testcaseCoveredMap[testcaseId]);
    // 算法一需要根据cases计算，因此调用以下方法生成相应数据
    if (mcdcKind === tests.mcdcKind.masking) {
      mergeDecisionCasesTestIds(funcCfg);
    }
    if (!mergeCoverage) {
      const {
        statementCovered,
        statementTotal,
        branchCovered,
        branchTotal,
        mcdcCovered,
        mcdcTotal,
        conditionCovered,
        conditionTotal,
        decisionCovered,
        decisionTotal,
      } = getCoverage(funcCfg, mcdcKind);
      result.push({
        statementCovered,
        statementTotal,
        branchCovered,
        branchTotal,
        mcdcCovered,
        mcdcTotal,
        conditionCovered,
        conditionTotal,
        decisionCovered,
        decisionTotal,
      });
    }
  });
  if (mergeCoverage) {
    const {
      statementCovered,
      statementTotal,
      branchCovered,
      branchTotal,
      mcdcCovered,
      mcdcTotal,
      conditionCovered,
      conditionTotal,
      decisionCovered,
      decisionTotal,
    } = getCoverage(funcCfg, mcdcKind);
    result.push({
      statementCovered,
      statementTotal,
      branchCovered,
      branchTotal,
      mcdcCovered,
      mcdcTotal,
      conditionCovered,
      conditionTotal,
      decisionCovered,
      decisionTotal,
    });
  }
  return result;
}
/**
 * 处理集成测试解决方案全局变量和绝对地址目标分析结果
 * @param {Object} version 版本信息
 * @param {object} entryFunctionVariables 入口函数变量信息
 * @param {Array[Object]} invokeFuncs 参与集成函数
 * @param {Map} invokeFunctionsVariablesMap 参与集成函数的变量信息
 * @param {Map} filesOfVersionMap 文件map
 * @param {Map} globalsDefinedOfVersionMap 整个版本里所有已定义全局变量
 */
async function handleIntegratedGlobalsAnalysis(version,
  entryFunctionVariables,
  invokeFuncs,
  invokeFunctionsVariablesMap,
  filesOfVersionMap,
  globalsDefinedOfVersionMap) {
  const integratedGlobals = [];
  let entryFuncOriginalGlobals = {};
  if (!globalsDefinedOfVersionMap) {
    globalsDefinedOfVersionMap = new Map();
    const globalsOfVersion = await FileGlobalVariable.find({ versionId: version._id, globals: { $exists: true } }, {
      globals: 1,
      fileId: 1,
    }).lean();
    globalsOfVersion.forEach(({ fileId, globals }) => {
      Object.keys(globals).forEach((globalName) => {
        if (!globalsDefinedOfVersionMap.has(globalName)) {
          globalsDefinedOfVersionMap.set(globalName, { defined: [], other: [] });
        }
        // excludes static global file
        if (globals[globalName]['@attributes'].includes('isStatic')) {
          return;
        }
        if (filesOfVersionMap.has(fileId.toString())) {
          const file = filesOfVersionMap.get(fileId.toString());
          const globalData = globals[globalName];
          const key = globalData['@attributes'].includes('isDefined') ? 'defined' : 'other';
          globalsDefinedOfVersionMap.get(globalName)[key].push({
            fileId: file._id.toString(),
            fileName: file.fileName,
            path: file.path,
            type: globalData['@type'],
          });
        }
      });
    });
  }
  entryFunctionVariables.variables = entryFunctionVariables.variables || {};
  entryFunctionVariables.externalGlobals = entryFunctionVariables.externalGlobals || [];
  const {
    variables, fileId, externalGlobals, functionId,
  } = entryFunctionVariables;
  if (variables.global) {
    entryFuncOriginalGlobals = JSON.parse(JSON.stringify(variables.global));
  }
  const newEntryFunctionVariables = await functionVariableSpliceFileId({
    fileId: fileId.toString(),
    variables: { global: variables.global || {} },
    externalGlobals,
  },
  {
    isOriginal: true,
    entryFuncFileId: fileId.toString(),
    allFileMap: filesOfVersionMap,
  });
  // handle integrattion function variable
  await utility.arrayChunkOperation(invokeFuncs, 10, async (invokeFunc) => {
    if (!invokeFunc.enableStub
      && invokeFunc.functionId
      && invokeFunctionsVariablesMap.has(invokeFunc.functionId.toString())
      && invokeFunc.functionId.toString() !== functionId.toString()) {
      let invokeFuncVariable = invokeFunctionsVariablesMap.get(invokeFunc.functionId.toString());
      invokeFuncVariable = await functionVariableSpliceFileId({
        fileId: invokeFuncVariable.fileId.toString(),
        variables: { global: invokeFuncVariable.variables && invokeFuncVariable.variables.global ? invokeFuncVariable.variables.global : {} },
        externalGlobals: invokeFuncVariable.externalGlobals,
      },
      {
        isOriginal: false,
        entryFuncFileId: fileId.toString(),
        allFileMap: filesOfVersionMap,
        spliceFileId: true,
      });
      mergeFunctionVariables(newEntryFunctionVariables, invokeFuncVariable);
    }
  });
  const functionVariableGlobals = newEntryFunctionVariables.variables.global || {};
  const globalKeys = Object.keys(functionVariableGlobals).sort();
  globalKeys.forEach((globalName) => {
    const integratedGlobal = functionVariableGlobals[globalName];
    const originalName = integratedGlobal.originalName ? integratedGlobal.originalName : integratedGlobal['@name'];
    const globalFiles = globalsDefinedOfVersionMap.get(originalName);
    const newGlobal = {
      id: globalName,
      globalName: originalName,
      globalType: integratedGlobal['@type'],
      fileId: integratedGlobal.fileId.toString(),
      filePath: integratedGlobal.filePath,
      fileName: path.basename(integratedGlobal.filePath),
      integrationAnalysis: integrationAnalysisResult.undefined,
      files: [],
    };
    if (integratedGlobal.isExternalGlobal) {
      newGlobal.isExternalGlobal = integratedGlobal.isExternalGlobal;
    } else if (
      (!entryFuncOriginalGlobals[globalName] || !entryFuncOriginalGlobals[globalName].fileId)
      && globalFiles && globalFiles.defined.length > 0) {
      // 全局变量在没有自定义选择默认文件的情况下优先使用定义该全局变量的文件(如果有多个定义文件且入口函数文件也定义了此全局变量就优先使用入口函数文件)
      const [currentFile] = globalFiles.defined.filter((f) => f.fileId.toString() === fileId.toString());
      const file = currentFile || globalFiles.defined[0];
      newGlobal.fileId = file.fileId.toString();
      newGlobal.filePath = file.path;
      newGlobal.fileName = path.basename(file.path);
      newGlobal.globalType = file.type;
      newGlobal.integrationAnalysis = integrationAnalysisResult.defined;
    }
    if (integratedGlobal['@attributes'] && integratedGlobal['@attributes'].includes('isStatic')) {
      newGlobal.isStatic = true;
      newGlobal.isDefine = true;
      newGlobal.integrationAnalysis = integrationAnalysisResult.defined;
      delete newGlobal.files;
    }
    if (globalFiles) {
      newGlobal.files = [...globalFiles.defined, ...globalFiles.other];
      newGlobal.definedFiles = globalFiles.defined;
      const [currentFile] = globalFiles.defined.filter((f) => f.fileId === newGlobal.fileId);
      if (currentFile) {
        newGlobal.integrationAnalysis = integrationAnalysisResult.defined;
      }
    }
    newGlobal.insideProject = filesOfVersionMap.get(newGlobal.fileId).kind === fileModels.kind.file;
    integratedGlobals.push(newGlobal);
  });
  // handle global duplicateDefined
  integratedGlobals.forEach((currentGlobal) => {
    const duplicateDefinedInfoSet = new Set();
    if (currentGlobal.definedFiles && !currentGlobal.isStatic) {
      currentGlobal.definedFiles.forEach((definedFile) => {
        integratedGlobals.forEach((originalGlobal) => {
          if (originalGlobal.fileId.toString() === definedFile.fileId && definedFile.fileId !== currentGlobal.fileId) {
            duplicateDefinedInfoSet.add(`${originalGlobal.originalName}(${currentGlobal.filePath})与${currentGlobal.globalName}(${definedFile.path})重定义;`);
          }
        });
        // global variable defined in entry func file
        if (definedFile.fileId !== currentGlobal.fileId && definedFile.fileId === fileId.toString()) {
          duplicateDefinedInfoSet.add(`${currentGlobal.globalName}(${currentGlobal.filePath})与${currentGlobal.globalName}(${definedFile.path})重定义;`);
        }
      });
      delete currentGlobal.definedFiles;
    }
    if (duplicateDefinedInfoSet.size > 0) {
      currentGlobal.integrationAnalysis = integrationAnalysisResult.duplicateDefined;
      currentGlobal.duplicateDefinedInfo = Array.from(duplicateDefinedInfoSet).toString();
    }
  });
  return integratedGlobals;
}
/**
 * 处理集成方案函数分析结果
 * @param {Array[Object]} funcs 集成方案的函数
 * @param {Map} fileToDefinedFunctionMap  文件id-[函数mangleId]
 * @param {Object} entryFuncFile  入口函数文件信息{fileId：'',filePath:'',includeFiles: ""}
 * 1:静态函数不做重定义判断
 * 2:函数定义与否需要需要根据选择的文件来判断
 * 3：函数重定义判断
 *      当前函数在所选择的文件之外的参与集成文件的中只要有其中一个文件定义该函数，
 *      那么该函数就被认为重定义。
 * 4：如果集成函数属于关联解析出来的函数，该函数选择了实际定义它的文件，
 *     且该文件为非入口函数文件并且属于已解析状态，此时该函数也属于重定义
 */
function handleInIntegratedFuncsAnalysis(funcs, fileToDefinedFunctionMap, filesMap, entryFuncFile) {
  const mangleIdToFileIdsMap = new Map();
  let existDuplicateDefined = false;
  fileToDefinedFunctionMap.forEach((mangledIds, fileId) => {
    if (mangledIds.length === 0) {
      return;
    }
    mangledIds.forEach((mangledId) => {
      if (!mangleIdToFileIdsMap.has(mangledId)) {
        mangleIdToFileIdsMap.set(mangledId, [fileId]);
      } else {
        mangleIdToFileIdsMap.get(mangledId).push(fileId);
      }
    });
  });
  funcs.forEach((func) => {
    const {
      filePath, isStatic, isDefine, mangledId, fileId,
    } = func;
    const duplicateDefinedInfo = new Set();
    func.integrationAnalysis = integrationAnalysisResult.defined;
    if (!isDefine) {
      func.integrationAnalysis = integrationAnalysisResult.undefined;
    }
    if (!fileId || !filePath || isStatic || !mangleIdToFileIdsMap.has(mangledId)) {
      return;
    }
    const duplicateDefinedFileIds = mangleIdToFileIdsMap.get(mangledId);
    duplicateDefinedFileIds.forEach((id) => {
      if (id === fileId.toString()) {
        return;
      }
      [...funcs, entryFuncFile].forEach((originalFunc) => {
        if (originalFunc.filePath && originalFunc.fileId && originalFunc.fileId.toString() === id) {
          duplicateDefinedInfo.add(`${func.functionName}(${filePath})与${func.functionName}(${originalFunc.filePath})重定义;`);
        }
      });
    });
    // 处理关联解析函数重定义状态
    if (isDefine
      && fileId.toString() !== entryFuncFile.fileId.toString()
      && entryFuncFile.includeFiles
      && entryFuncFile.includeFiles[fileId.toString()]
      && filesMap.has(fileId.toString())
      && filesMap.get(fileId.toString()).status === fileModels.statusKind.compiled) {
      duplicateDefinedInfo.add(`${func.functionName}(${filesMap.get(fileId.toString()).path})与${func.functionName}(${entryFuncFile.filePath})重定义;`);
    }
    if (duplicateDefinedInfo.size > 0) {
      existDuplicateDefined = true;
      func.integrationAnalysis = integrationAnalysisResult.duplicateDefined;
      func.duplicateDefinedInfo = Array.from(duplicateDefinedInfo).toString();
    }
  });
  return { existDuplicateDefined };
}
/**
 * 集成测试获取集成方案
 * @param {String|ObjectId} functionId  入口函数id
 * @param {Object} version  版本信息
 * @param {Boolean} includeGlobal  获取的数据包含全局变量
 * @param {Boolean} includeFixedAddressBases 获取的数据包含绝对地址目标
 */
async function integrationSolution(functionId, version, includeGlobal = true, includeFixedAddressBases = true) {
  const resSolution = {
    functions: [],
    globals: [],
    maxDepth: 1,
    duplicateDefined: false,
  };
  let relationFuncs = await invokeRelations.retrieveRelations(functionId,
    {
      sort: true,
      initRelations: true,
      completeDepth: true,
    });
  // 集成函数按边去重
  relationFuncs = await relationFuncsDeduplicate(relationFuncs, version._id);
  const stubsOfVersion = await getFunctionStubsByVersionId(version._id);
  const functionVariable = await FunctionVariable.findOne({ functionId });
  if (!functionVariable) {
    return resSolution;
  }
  const entryFuncFileId = functionVariable.fileId.toString();
  if (includeGlobal) {
    resSolution.entryFunctionOriginalVariables = {
      fileId: entryFuncFileId,
      stubs: functionVariable.stubs || {},
    };
  }
  const filesMap = await fileModels.getSourceFileByVersionId(version._id, null, ['_id', 'path', 'fileName', 'status', 'includeFiles', 'kind']);
  const {
    variableMap, toFuncMangleMap, fileToDefinedFunctionMap,
  } = await getInvokeFunctionsVariable(
    functionId,
    relationFuncs,
    stubsOfVersion,
    version._id,
    entryFuncFileId,
    true,
  );
  const definedFunctionsIds = new Set();
  definedFunctionsIds.add(functionId.toString());
  const newRelationFuncs = [];
  relationFuncs.forEach((func) => {
    if (func.toFuncId) {
      definedFunctionsIds.add(func.toFuncId.toString());
    }
    if (!definedFunctionsIds.has(func.fromFuncId.toString())) {
      definedFunctionsIds.add(func.fromFuncId.toString());
    }
    const newFunc = formatFuncInvoke(func,
      {
        entryFuncVariables: functionVariable,
        allFileMap: filesMap,
        variableMap,
        toFuncMangleMap,
        includeVariable: false,
      });
    if (newFunc.isCreateManual) {
      // 移除参与集成函数中的手动添加的桩函数
      if (newFunc.fromFuncId.toString() !== functionId) {
        return;
      }
      // 手动添加的桩函数默认选择入口函数的文件
      const file = filesMap.get(entryFuncFileId);
      newFunc.fileId = entryFuncFileId;
      newFunc.filePath = file.path;
      newFunc.fileName = file.fileName;
    }
    let invokeLevel = 1;
    if (func.depth && func.depth.depths) {
      resSolution.maxDepth = Math.max(resSolution.maxDepth, func.depth.maxDepth || func.depth.minDepth);
      invokeLevel = [...func.depth.depths.values()].toString();
    }
    newFunc.invokeLevel = invokeLevel;
    newRelationFuncs.push(newFunc);
  });
  const definedFunctionsMap = new Map();
  // 获取已定义的函数和模版函数信息
  (await Func.find({
    $or: [
      { _id: { $in: Array.from(definedFunctionsIds) } },
      { kind: { $in: fileElement.getTemplateElementKind() } },
    ],
  }, [
    'functionFullName',
    'functionName',
    'language',
    'rawTemplateId',
    'functionAttrs',
    'fileId',
    'kind']).lean()).forEach((func) => {
    definedFunctionsMap.set(func._id.toString(), func);
  });
  newRelationFuncs.forEach((invokeFunc) => {
    const toFuncData = invokeFunc.functionId && definedFunctionsMap.has(invokeFunc.functionId.toString())
      ? definedFunctionsMap.get(invokeFunc.functionId.toString()) : null;
    const fromFuncData = invokeFunc.fromFuncId && definedFunctionsMap.has(invokeFunc.fromFuncId.toString())
      ? definedFunctionsMap.get(invokeFunc.fromFuncId.toString()) : null;
    invokeFunc.id = uuid();
    // 标记修改指针目标字段
    invokeFunc.editPointFuncKey = invokeFunc.mangledId;
    if (fromFuncData && isCFunction(fromFuncData)) {
      invokeFunc.editPointFuncKey = invokeFunc.functionName;
    }
    // 已定义函数显示函数原型
    if (toFuncData) {
      invokeFunc.functionName = toFuncData.functionFullName;
      invokeFunc.isStatic = toFuncData.functionAttrs.includes('isStatic') && toFuncData.fileId.toString() === invokeFunc.fileId.toString();
    } else if (!invokeFunc.functionId && invokeFunc.nameparams) { // 未定义函数函数名使用nameparams
      invokeFunc.functionName = invokeFunc.nameparams;
      delete invokeFunc.nameparams;
    }
    delete invokeFunc.attributes;
    // 处理特化函数信息
    invokeFunc.isSpecialization = false;
    if (!invokeFunc.functionId) {
      return;
    }
    const invokeFunctionId = invokeFunc.functionId.toString();
    if (!definedFunctionsMap.has(invokeFunctionId)
      || definedFunctionsMap.get(invokeFunctionId).kind !== fileElement.elementKind.specialFunction
      || !definedFunctionsMap.get(invokeFunctionId).rawTemplateId) {
      return;
    }
    const rawTemplateFunction = definedFunctionsMap.get(definedFunctionsMap.get(invokeFunctionId).rawTemplateId.toString());
    if (!rawTemplateFunction) {
      return;
    }
    // 重置特化函数可选文件为第一层模版函数对应文件
    const { fileId, functionName } = rawTemplateFunction;
    const { fileName, path } = filesMap.get(fileId.toString());
    invokeFunc.fileId = fileId;
    invokeFunc.fileName = fileName;
    invokeFunc.functionName = functionName;
    invokeFunc.filePath = path;
    invokeFunc.isSpecialization = true;
    invokeFunc.files = [{ fileId, fileName, path }];
  });
  const entryFuncFile = filesMap.get(entryFuncFileId);
  const { existDuplicateDefined } = handleInIntegratedFuncsAnalysis(newRelationFuncs, fileToDefinedFunctionMap, filesMap,
    {
      fileId: entryFuncFileId,
      filePath: entryFuncFile.path,
      includeFiles: entryFuncFile.includeFiles,
    });
  if (includeGlobal) {
    resSolution.globals = await handleIntegratedGlobalsAnalysis(
      version,
      functionVariable,
      newRelationFuncs,
      variableMap,
      filesMap
    );
  }
  if (includeFixedAddressBases) {
    resSolution.fixedAddressBases = [];
    await IntegrationAddInvokeFuncFixedAddressBases(functionVariable, null, version.versionType, filesMap);
    let newFixedAddressBases = await FunctionVariable.findOne({ functionId, 'fixedAddrs.bases': { $exists: true } }, { 'fixedAddrs.bases': 1 });
    if (newFixedAddressBases) {
      newFixedAddressBases = newFixedAddressBases.fixedAddrs.bases || {};
      const allFiles = [];
      filesMap.forEach((value,) => {
        allFiles.push({
          fileId: value._id, path: value.path, fileName: value.fileName,
        });
      });
      Object.keys(newFixedAddressBases).forEach((keyName) => {
        const base = newFixedAddressBases[keyName];
        base.files = allFiles;
        base.id = base['@hex'];
        if (!base.fileId) {
          base.fileId = entryFuncFile._id;
          base.filePath = entryFuncFile.path;
          base.fileName = entryFuncFile.fileName;
        }
        if (base.filePath && !base.fileName) {
          base.fileName = path.basename(base.filePath);
        }
        base.insideProject = filesMap.get(base.fileId.toString()).kind === fileModels.kind.file;
        resSolution.fixedAddressBases.push(base);
      });
    }
  }
  resSolution.functions = newRelationFuncs;
  resSolution.duplicateDefined = existDuplicateDefined;
  return resSolution;
}
/**
 * 更新解决方案变量文件信息
 * @param {String|ObjectId} functionId 入口函数id
 * @param {String} variableName 变量名
 * @param {Boolean} isExternalGlobal true:外部变量 false:非外部变量
 * @param {String|ObjectId} fileId 文件id
 * @param {String} filePath 文件路径
 */
async function integrationSolutionUpdateFile(functionId, type, variableName, isExternalGlobal, fileId, filePath) {
  const functionVariables = await FunctionVariable.findOne({ functionId }, ['variables', 'externalGlobals', 'fileId', 'fixedAddrs.bases']);
  const func = await Func.findById(functionId);
  if (!filePath) {
    const file = await File.findById(fileId);
    if (!file) {
      throw new Error(`file:${fileId} not found`);
    }
    filePath = file.path;
  }
  const doc = { $set: {} };
  const testcaseDoc = { $set: {} };
  switch (type) {
    case integrationSolutionVariableType.global:
    {
      if (isExternalGlobal) {
        functionVariables.externalGlobals = functionVariables.externalGlobals || [];
        functionVariables.externalGlobals.map((g) => {
          if (g.fullname === variableName) {
            g.fileId = mongoose.Types.ObjectId(fileId);
          }
          return g;
        });
        doc.$set.externalGlobals = functionVariables.externalGlobals;
        break;
      }
      const fileGlobalVariable = await FileGlobalVariable.findOne(
        { fileId, globals: { $exists: true } },
        ['globals', 'fileId']
      );
      const fileGlobals = fileGlobalVariable ? fileGlobalVariable.globals : null;
      if (!fileGlobals || !fileGlobals[variableName]) {
        break;
      }
      const fileGlobal = fileGlobals[variableName];
      doc.$set[`variables.global.${variableName}`] = {
        '@attributes': fileGlobal['@attributes'],
        '@type': fileGlobal['@type'],
        '@mangled': fileGlobal['@mangled'],
        '@name': fileGlobal['@name'],
        '@fullname': fileGlobal['@fullname'],
        fileId,
        filePath,
      };
      testcaseDoc.$set[`data.variables.global.${variableName}.fileId`] = fileId;
      testcaseDoc.$set[`data.variables.global.${variableName}.filePath`] = filePath;
      break;
    }
    case integrationSolutionVariableType.fixedAddrBases:
    {
      if (utility.isHexadecimal(variableName)) {
        variableName = utility.convertHexToDec(variableName);
      }
      if (!functionVariables
          || !functionVariables.fixedAddrs
          || !functionVariables.fixedAddrs.bases
          || !functionVariables.fixedAddrs.bases[variableName]
          || (functionVariables.fixedAddrs.bases[variableName].fileId && functionVariables.fixedAddrs.bases[variableName].fileId === fileId)
          || (!functionVariables.fixedAddrs.bases[variableName].fileId && fileId === func.fileId.toString())) {
        break;
      }
      doc.$set[`fixedAddrs.bases.${variableName}.fileId`] = fileId;
      doc.$set[`fixedAddrs.bases.${variableName}.filePath`] = filePath;
      break;
    }
    default: break;
  }
  await Promise.all([
    FunctionVariable.updateOne({ functionId }, doc),
    TestCase.updateMany({ functionId }, testcaseDoc),
    tests.removeResultOfExecutionInFunction(func),
  ]);
}

/**
 * 手动覆盖node或decision case
 * @param {ObjectId} versionId
 * @param {ObjectId} fileId
 * @param {ObjectId} functionId
 * @param {String} nodeId
 * @param {Object} opt
 * @param {String} opt.decisionId
 * @param {String} opt.condKey
 * @param {String} opt.description
 */
async function manuallyCover(versionId, fileId, functionId, nodeId, {
  decisionId = null,
  condKey = null,
  description,
}) {
  if (decisionId) {
    if (!condKey) {
      throw new Error('need condKey when decisionId is presented');
    }
    await ManualCovered.updateOne({
      versionId, fileId, functionId, nodeId, decisionId, conditionKey: condKey,
    }, {
      $set: {
        description,
        [`coveredFields.decisions|${decisionId}|coveredConditions|${condKey}`]: 1,
      },
    }, { upsert: true });
  } else {
    if (condKey) {
      throw new Error('do not need condKey when decisionId is not presented');
    }
    const { nodes, edges, branches } = await getFunctionCFG(functionId);
    const coveredFields = {};
    const currentNode = nodes[nodeId];
    if (!currentNode) {
      throw new Error(`node id ${nodeId} not found`);
    }
    coveredFields[`coveredFields.nodes|${nodeId}`] = 1;
    if (currentNode.statements) {
      Object.keys(currentNode.statements).forEach((statementKey) => {
        coveredFields[`coveredFields.nodes|${nodeId}|statements|${statementKey}`] = 1;
      });
    }
    const coveredEdges = [];
    Object.values(edges).forEach((edge) => {
      if (edge.endNodeId.toString() === nodeId.toString()) {
        // 前置边
        const preNode = nodes[edge.startNodeId];
        if (preNode.testIds && Object.keys(preNode.testIds).length > 0) {
          // 前置节点已覆盖，所以前置边也需要被覆盖
          coveredEdges.push(edge);
        }
      } else if (edge.startNodeId.toString() === nodeId.toString()) {
        // 后置边
        const postNode = nodes[edge.endNodeId];
        if (postNode.testIds && Object.keys(postNode.testIds).length > 0) {
          // 后置节点已覆盖，所以后置边也需要被覆盖
          coveredEdges.push(edge);
        }
      }
    });
    coveredEdges.forEach((edge) => {
      coveredFields[`coveredFields.edges|${edge.startNodeId}->${edge.endNodeId}`] = 1;
      // 相应的branch也需要覆盖
      // eslint-disable-next-line no-restricted-syntax
      for (const branchId of Object.keys(branches)) {
        const branch = branches[branchId];
        if (branch.nodeId.toString() === edge.startNodeId.toString()) {
          if (branch.branches && branch.branches[edge.endNodeId]) {
            coveredFields[`coveredFields.branches|${branchId}|branches|${edge.endNodeId}`] = 1;
          }
        }
      }
    });
    await ManualCovered.updateOne({
      versionId, fileId, functionId, nodeId, decisionId: null, conditionKey: null,
    }, {
      $set: {
        description,
        ...coveredFields,
      },
    }, { upsert: true });
  }
  await updateFunctionCoverage(functionId);
}

/**
 * 删除手动覆盖node或decision case
 * @param {ObjectId} functionId
 * @param {String} nodeId
 * @param {Object} opt
 * @param {String} opt.decisionId
 * @param {String} opt.condKey
 */
async function manuallyUnCover(functionId, nodeId, {
  decisionId = null,
  condKey = null,
} = {}) {
  const { n } = await ManualCovered.deleteOne({
    functionId,
    nodeId,
    decisionId,
    conditionKey: condKey,
  });
  if (n === 0) {
    throw new PhoenixError('not found', 'not exists');
  }
  await updateFunctionCoverage(functionId);
}

async function isCompatible(functionId) {
  let compatible = true;
  const func = getFunction(functionId);
  if (!func.versionKey || func.versionKey !== compatibleKey) {
    compatible = false;
    await Func.updateOne({ _id: mongoose.Types.ObjectId(functionId) }, { $set: { compatibleKey } });
  }
  return compatible;
}

async function getTreeFuncs(fileIds, hasCodeDefect) {
  const funcs = await Func.find({
    fileId: { $in: fileIds },
  }).lean();
  const testcases = await TestCase.find({ fileId: { $in: fileIds } }, ['resultKind', 'functionId']).lean();
  const funcTestcasesMap = {};
  let funcCodeDefectsMap = {};
  testcases.forEach((testcase) => {
    if (!funcTestcasesMap[testcase.functionId.toString()]) {
      funcTestcasesMap[testcase.functionId.toString()] = [];
    }
    funcTestcasesMap[testcase.functionId.toString()].push({ r: testcase.resultKind });
  });
  if (hasCodeDefect) {
    funcCodeDefectsMap = await codeDefects.getCodeDefectsGroup('functionId', { fileIds });
  }
  funcs.forEach((func) => {
    func.testResults = [];
    func.hasCodeDefects = false;
    if (funcTestcasesMap[func._id.toString()]) {
      func.testResults = funcTestcasesMap[func._id.toString()];
    }
    if (funcCodeDefectsMap[func._id.toString()] !== undefined) {
      func.hasCodeDefects = true;
    }
  });
  return funcs;
}

async function getDynamicStatistics(functionId, fileId = null) {
  const res = {
    funcsCount: 1,
    testsCount: 0,
    testedFuncsCount: 0,
    testedTestsCount: 0,
  };
  const filter = {};
  if (functionId) {
    filter.functionId = mongoose.Types.ObjectId(functionId);
  } else if (fileId) {
    filter.fileId = mongoose.Types.ObjectId(fileId);
  } else {
    return res;
  }
  if (fileId) {
    res.funcsCount = await Func.countDocuments(filter);
  }
  const testcases = await TestCase.find(filter, ['resultKind', 'functionId']).lean();
  const testedFunc = {};
  testcases.forEach((testcase) => {
    if (testcase.resultKind !== tests.resultKind.unknown) {
      res.testedTestsCount += 1;
      testedFunc[testcase.functionId.toString()] = 1;
    }
  });
  res.testedFuncsCount = Object.keys(testedFunc).length;
  res.testsCount = testcases.length;
  return res;
}

/**
 *  根据测试用例获取函数覆盖率
 * @param {String} functionId 函数id
 * @param {Array[String]} testcaseIds 测试用例id数组
 * @param {Object} version 版本信息
 */
async function getFunctionCoverages(functionId, testcaseIds, version) {
  const resCoverages = {};
  if (!version || !testcaseIds || testcaseIds.length === 0) {
    return resCoverages;
  }
  const { coverages, mcdcKind, versionType } = version;
  const funcCoverages = await getTestcasesCoverage(functionId, testcaseIds, mcdcKind, true);
  if (!funcCoverages || funcCoverages.length === 0) {
    return coverages;
  }
  const [funcCoverage] = funcCoverages;
  if (versionType === projectVersions.versionType.integrationTest) {
    const functionCfg = await getFunctionCFG(functionId);
    const func = await Func.findById(functionId);
    const { invokedFuncCovered, invokeCovered } = getInvokeCoverage({ invokes: functionCfg.invokes, testcaseIds });
    funcCoverage.invokedFuncTotal = func.invokedFuncTotal;
    funcCoverage.invokedFuncCovered = invokedFuncCovered;
    funcCoverage.invokeTotal = func.invokeTotal;
    funcCoverage.invokeCovered = invokeCovered;
  }
  function handleConveragePercent(coverage) {
    return `${Math.round(coverage * 10000) / 100}%`;
  }
  // 语句覆盖
  if ((coverages & tests.coverageKind.statement) > 0) {
    resCoverages.statementCoverage = '-';
    if (funcCoverage.statementTotal) {
      resCoverages.statementCoverage = handleConveragePercent(funcCoverage.statementCovered / funcCoverage.statementTotal);
    }
  }
  // 分支覆盖
  if ((coverages & tests.coverageKind.branch) > 0) {
    resCoverages.branchCoverage = '-';
    if (funcCoverage.branchTotal) {
      resCoverages.branchCoverage = handleConveragePercent(funcCoverage.branchCovered / funcCoverage.branchTotal);
    }
  }
  // mcdc覆盖
  if ((coverages & tests.coverageKind.mcdc) > 0) {
    resCoverages.mcdcCoverage = '-';
    if (funcCoverage.mcdcTotal) {
      resCoverages.mcdcCoverage = handleConveragePercent(funcCoverage.mcdcCovered / funcCoverage.mcdcTotal);
    }
  }
  // 条件覆盖
  if ((coverages & tests.coverageKind.condition) > 0) {
    resCoverages.conditionCoverage = '-';
    if (funcCoverage.conditionTotal) {
      resCoverages.conditionCoverage = handleConveragePercent(funcCoverage.conditionCovered / funcCoverage.conditionTotal);
    }
  }
  // 决策覆盖
  if ((coverages & tests.coverageKind.decision) > 0) {
    resCoverages.decisionCoverage = '-';
    if (funcCoverage.decisionTotal) {
      resCoverages.decisionCoverage = handleConveragePercent(funcCoverage.decisionCovered / funcCoverage.decisionTotal);
    }
  }
  // 函数覆盖
  if ((coverages & tests.coverageKind.invokedFunc) > 0) {
    resCoverages.invokedFuncCoverage = '-';
    if (funcCoverage.invokedFuncTotal) {
      resCoverages.invokedFuncCoverage = handleConveragePercent(funcCoverage.invokedFuncCovered / funcCoverage.invokedFuncTotal);
    }
  }
  // 调用覆盖
  if ((coverages & tests.coverageKind.invoke) > 0) {
    resCoverages.invokeCoverage = '-';
    if (funcCoverage.invokeTotal) {
      resCoverages.invokeCoverage = handleConveragePercent(funcCoverage.invokeCovered / funcCoverage.invokeTotal);
    }
  }
  return resCoverages;
}

async function getIntegrationCoveredDetail(functionId, versionId) {
  const funcs = await getFunctions(
    mongoose.Types.ObjectId(versionId),
    {
      kinds: [fileElement.elementKind.function, fileElement.elementKind.specialFunction],
      projection: { _id: 1, kind: 1, insideProject: 1 },
    }
  );
  const specFuncIds = new Set();
  const insideProjectFuncIds = new Set();
  funcs.forEach((func) => {
    if (func.kind === fileElement.elementKind.specialFunction) {
      specFuncIds.add(func._id.toString());
    }
    if (func.insideProject) {
      insideProjectFuncIds.add(func._id.toString());
    }
  });
  const relations = await invokeRelations.retrieveRelations(functionId, { includeCtor: false });
  const toFuncIdSet = new Map();
  const entryFunc = await getFunction(functionId);
  toFuncIdSet.set(functionId, { id: functionId, name: entryFunc.functionFullName });
  const stubMangled = new Set();
  relations.forEach((relation) => {
    if (relation.enableStub) {
      stubMangled.add(relation.toFuncMangledId);
      return;
    }
    if (!relation.toFuncId) {
      return;
    }
    if (relation.toFuncPointer === true || relation.toFuncCtor === true) {
      return;
    }
    if (relation.toFuncId.toString() === functionId) {
      return;
    }
    // 项目外函数过滤
    if (!insideProjectFuncIds.has(relation.toFuncId.toString())) {
      return;
    }
    toFuncIdSet.set(
      relation.toFuncId.toString(),
      { id: relation.toFuncId.toString(), name: relation.toFuncName, fromMangled: relation.fromFuncMangledId }
    );
  });
  const filter = { functionId: mongoose.Types.ObjectId(functionId) };
  const testcases = await tests.getTestsOfFunctions(functionId);
  filter.testcaseId = { $in: testcases.map((test) => test._id) };
  const testcasesCovered = await TestCaseCoveredField.find(filter);
  const coveredFields = {};
  testcasesCovered.forEach((covered) => {
    Object.keys(covered.coveredFields).forEach((key) => {
      coveredFields[key] = 1;
    });
  });
  return Promise.all([...toFuncIdSet.values()].map(async (func) => {
    const funcVariable = await FunctionCFG.findOne({ functionId: mongoose.Types.ObjectId(func.id) }, ['callexpr']);
    const validCallExprs = [];
    func.covered = false;
    if (coveredFields[`invokes|${func.id}`]) {
      func.covered = true;
    }
    if (funcVariable && funcVariable.callexpr && utility.isArrayType(funcVariable.callexpr)) {
      funcVariable.callexpr.forEach((callExpr) => {
        // 打桩函数不记入调用覆盖
        if (callExpr.mangled && stubMangled.has(callExpr.mangled)) {
          return;
        }
        callExpr.covered = false;
        if (coveredFields[`invokes|${func.id}|callexpr|${callExpr.id}`]) {
          callExpr.covered = true;
        }
        validCallExprs.push(callExpr);
      });
    }
    toFuncIdSet.get(func.id).callexpr = validCallExprs;
    return func;
  }));
}

function generateLineData(column, data) {
  const line = [];
  let currentLimit = 0;
  let currentPos = 0;
  column.forEach((length, idx) => {
    line.push(`${data[idx]} `);
    currentLimit += length;
    currentPos += data[idx].length + 1;
    if (currentPos < currentLimit) {
      line.push(' '.repeat(currentLimit - currentPos));
      currentPos = currentLimit;
    }
  });
  return line.join('');
}

async function generateHeadComment(functionId, functionInfoMap, typeSysMap, versionRelations) {
  const func = await getFunction(functionId);
  if (!func) {
    return Promise.resolve();
  }
  const currentFileId = func.fileId.toString();
  const relations = await functionRelations.retrieveRelations(functionId, func.versionId, versionRelations);
  const integratedFuncsSet = new Set([functionId.toString()]);
  const funcIdSet = new Set();
  const fileIdSet = new Set();
  if (!functionInfoMap.has(functionId.toString())) {
    funcIdSet.add(functionId.toString());
  }
  if (!typeSysMap.has(func.fileId.toString())) {
    fileIdSet.add(func.fileId.toString());
  }
  const mangledFileMap = new Map();
  relations.forEach((relation) => {
    if (!relation.toFuncId) {
      return;
    }
    mangledFileMap.set(relation.toFuncMangledId, relation.toFuncFileId.toString());
    const funcId = relation.toFuncId.toString();
    const fileId = relation.toFuncFileId.toString();
    integratedFuncsSet.add(funcId);
    if (!functionInfoMap.has(funcId)) {
      funcIdSet.add(funcId);
    }
    if (!typeSysMap.has(fileId)) {
      fileIdSet.add(fileId);
    }
  });
  await utility.arrayChunkOperation([...fileIdSet], 5, async (fileId) => {
    const fileTypes = await fileModels.getFileTypeSystemByFile(fileId);
    typeSysMap.set(fileId, fileTypes && fileTypes.types ? fileTypes.types : {});
  });
  await utility.arrayChunkOperation([...funcIdSet], 5, async (funcId) => {
    const [functionVariable, functionCfg] = await Promise.all([
      FunctionVariable.findOne({ functionId: mongoose.Types.ObjectId(funcId) }),
      getFunctionCFG(funcId, null, false),
    ]);
    functionInfoMap.set(funcId, {
      variables: functionVariable ? functionVariable.toObject() : {},
      cfg: functionCfg || {},
      fileId: functionVariable ? functionVariable.fileId.toString() : '',
    });
  });
  const globals = {};
  const callExpr = {};
  integratedFuncsSet.forEach((funcId) => {
    const { cfg, variables, fileId } = functionInfoMap.get(funcId);
    if (variables && variables.variables && variables.variables.global) {
      Object.keys(variables.variables.global).forEach((key) => {
        if (!globals[key] || funcId === functionId.toString()) {
          const use = [];
          const typeSys = typeSysMap.has(fileId) ? typeSysMap.get(fileId) : {};
          const type = variables.variables.global[key]['@type'];
          if (variables.variables.global[key]['@attributes'].indexOf('isStatic') >= 0) {
            use.push('Static');
          }
          if (variables.variables.global[key]['@attributes'].indexOf('isExtern') >= 0
            && variables.variables.global[key]['@attributes'].indexOf('isDefined') < 0) {
            use.push('Imported');
          }
          if (typeSys[type]
            && typeSys[type]['@attributes'].indexOf('isConst') >= 0) {
            use.push('Const');
          }
          if (typeSys[type]
            && typeSys[type]['@pointeeType']
            && typeSys[typeSys[type]['@pointeeType']]
            && typeSys[typeSys[type]['@pointeeType']]['@attributes'].indexOf('isConst') >= 0
          ) {
            use.push('Point to const');
          }
          globals[key] = {
            type,
            usage: funcId === functionId.toString() ? 'Primary' : 'Secondary',
            use: use.join(','),
          };
        }
      });
    }
    if (cfg && cfg.callexpr) {
      cfg.callexpr.forEach((expr) => {
        if (!expr.name && !expr.mangled) {
          return;
        }
        let { mangled } = expr;
        if (!expr.mangled) {
          mangled = expr.name;
        }
        if (!callExpr[mangled]) {
          callExpr[mangled] = {
            name: expr.name,
            location: mangledFileMap.has(mangled) && mangledFileMap.get(mangled) === currentFileId ? 'This File' : '',
            times: 0,
          };
        }
        callExpr[mangled].times += 1;
      });
    }
  });
  const comment = [];
  comment.push('-'.repeat(120));
  comment.push('');
  comment.push(`Routine: ${func.functionName}`);
  comment.push('');
  comment.push(`Declared on Line: ${func.location.line}`);
  comment.push('');
  comment.push('');
  comment.push('Parameters:');
  comment.push('===========');
  comment.push('');
  comment.push(`Action${' '.repeat(2)}Name${' '.repeat(26)}Type${' '.repeat(36)}Usage${' '.repeat(15)}Use`);
  comment.push(`------${' '.repeat(2)}----${' '.repeat(26)}----${' '.repeat(36)}-----${' '.repeat(15)}---`);
  const currentVariable = functionInfoMap.has(functionId.toString()) ? functionInfoMap.get(functionId.toString()).variables : null;
  if (currentVariable && currentVariable.variables && currentVariable.variables.params) {
    Object.keys(currentVariable.variables.params).forEach((key) => {
      comment.push(generateLineData([8, 30, 40, 20, 30], ['', key, currentVariable.variables.params[key]['@type'], '', '']));
    });
  }
  comment.push('');
  comment.push('');
  comment.push('Return value:');
  comment.push('=============');
  comment.push('');
  comment.push(`Type${' '.repeat(94)}Use`);
  comment.push(`----${' '.repeat(94)}---`);
  if (currentVariable && currentVariable.output && currentVariable.output['%']) {
    comment.push(currentVariable.output['%']['@type']);
  } else {
    comment.push('void');
  }
  comment.push('');
  comment.push('');
  comment.push('Global Variables:');
  comment.push('=================');
  comment.push('');
  comment.push(`Action${' '.repeat(2)}Name${' '.repeat(26)}Type${' '.repeat(16)}Native${' '.repeat(14)}Usage${' '.repeat(15)}Use`);
  comment.push(`------${' '.repeat(2)}----${' '.repeat(26)}----${' '.repeat(16)}------${' '.repeat(14)}-----${' '.repeat(15)}---`);
  Object.keys(globals).forEach((key) => {
    comment.push(generateLineData([8, 30, 20, 20, 20, 30], ['', key, globals[key].type, 'Yes', globals[key].usage, globals[key].use]));
  });
  comment.push('');
  comment.push('');
  comment.push('Calls:');
  comment.push('======');
  comment.push('');
  comment.push(`Name${' '.repeat(46)}Location${' '.repeat(22)}Number of Calls${' '.repeat(15)}`);
  comment.push(`----${' '.repeat(46)}--------${' '.repeat(22)}---------------${' '.repeat(15)}`);
  Object.values(callExpr).forEach((expr) => {
    comment.push(generateLineData([50, 30, 30], [expr.name, expr.location, expr.times]));
  });
  comment.push('-'.repeat(120));
  return comment.map((line, idx) => {
    if (idx === 0) {
      return `/***${line}`;
    }
    if (idx === comment.length - 1) {
      return `***${line}*/`;
    }
    return `*** ${line}`;
  }).join('\n');
}

async function get502DesignFlowData(functionId, comments = []) {
  functionId = mongoose.Types.ObjectId(functionId);
  const lineCommentsMap = {};
  comments.forEach(({ line, comment }) => {
    lineCommentsMap[line] = comment;
  });
  const cfg = await FunctionCFG.findOne({ functionId });
  const variable = await FunctionVariable.findOne({ functionId });
  const mangledMap = {};
  if (variable.stubs) {
    Object.values(variable.stubs).forEach((stub) => {
      mangledMap[stub['@mangled']] = stub['@name'];
    });
  }
  const {
    callexpr,
    nodes,
    edges,
    branches,
  } = cfg;
  const flowData = {
    nodes: [],
    edges: [],
  };
  let nodeIdLoop = 1;
  let edgeIdLoop = 1;
  let startNodeId;
  const edgeLabelMap = {};
  if (branches) {
    Object.values(branches).forEach((b) => {
      Object.keys(b.branches).forEach((nid) => {
        // eslint-disable-next-line no-nested-ternary
        edgeLabelMap[`${b.nodeId}|${nid}`] = b.branches[nid].kind === 'BOOLEAN' ? (b.branches[nid].value === '1' ? 'Y' : 'N') : b.branches[nid].value;
      });
    });
  }
  const nodesMap = {};
  Object.values(nodes).forEach((n) => {
    if (nodesMap[n.nodeId] === undefined) {
      nodesMap[n.nodeId] = [];
    }
    if (n.kind === cfgNodeKind.entry) {
      startNodeId = n.nodeId;
      nodesMap[n.nodeId].push({
        id: nodeIdLoop += 1,
        label: 'Start',
        type: 'Start',
      });
    } else if (n.kind === cfgNodeKind.exit) {
      nodesMap[n.nodeId].push({
        id: nodeIdLoop += 1,
        label: 'End',
        type: 'End',
      });
    }
    if (callexpr) {
      callexpr.reverse().forEach((expr) => {
        if (!expr.mangled) {
          return;
        }
        if (expr.begin.line >= n.begin.line
        && expr.begin.column >= n.begin.column
        && expr.end.line <= n.end.line
        && expr.end.column <= n.end.column) {
          nodesMap[n.nodeId].push({ id: nodeIdLoop += 1, label: [lineCommentsMap[expr.begin.line] || '', mangledMap[expr.mangled] || ''].filter((i) => i).join('\n'), type: 'Process' });
        }
      });
    }
    if ([cfgNodeKind.conditionIf, cfgNodeKind.conditionSwitch].indexOf(n.kind) >= 0) {
      nodesMap[n.nodeId].push({ id: nodeIdLoop += 1, label: lineCommentsMap[n.begin.line] || '', type: 'Decision' });
    }
  });
  const edgesMap = {};
  Object.values(edges).forEach((e) => {
    if (edgesMap[e.startNodeId] === undefined) {
      edgesMap[e.startNodeId] = [];
    }
    edgesMap[e.startNodeId].push(e.endNodeId);
  });
  const processedNodes = new Set();
  function resc(nodeId, pre = null, label = '') {
    if (nodesMap[nodeId].length > 0) {
      if (pre !== null) {
        flowData.edges.push({
          id: edgeIdLoop += 1,
          label,
          from: pre,
          to: nodesMap[nodeId][0].id,
        });
        label = '';
      }
      if (processedNodes.has(nodeId)) {
        return;
      }
      processedNodes.add(nodeId);
      nodesMap[nodeId].forEach((n, idx) => {
        flowData.nodes.push({
          id: n.id,
          label: n.label,
          type: n.type,
        });
        if (idx > 0) {
          flowData.edges.push({
            id: edgeIdLoop += 1,
            label: '',
            from: pre,
            to: n.id,
          });
        }
        pre = n.id;
      });
    } else {
      if (processedNodes.has(nodeId)) {
        return;
      }
      processedNodes.add(nodeId);
    }
    if (edgesMap[nodeId] === undefined) {
      return;
    }
    edgesMap[nodeId].forEach((id) => resc(id, pre, label || edgeLabelMap[`${nodeId}|${id}`] || ''));
  }
  resc(startNodeId);
  return flowData;
}
/**
 *  根据文件行获取文件中的函数
 * @param {String|ObjectId} fileId 文件id
 * @param {Number} line  文件内容中的行
 * @returns null | Function {'_id', 'functionName', 'location', 'endLocation'}
 */
async function getFunctionByFileLine(fileId, line) {
  line = parseInt(line, 10);
  const funcs = await Func.find({
    fileId: mongoose.Types.ObjectId(fileId),
    kind: { $in: fileElement.getCountableKinds() },
  },
  ['_id', 'functionName', 'location', 'endLocation']).sort({ index: 1 }).lean();
  if (funcs.length === 0) {
    return null;
  }
  const func = funcs.find((f) => f.location.line === line || (line > f.location.line && line <= f.endLocation.line));
  return func;
}
eventHub.on([eventKind.EVENT_FILE_REMOVE, eventKind.EVENT_FILE_RESET], async ({ fileIds, versionId }) => {
  const filter = { versionId: mongoose.Types.ObjectId(versionId) };
  let functionIds = null;
  if (fileIds) {
    filter.fileId = { $in: [].concat(fileIds).map((id) => mongoose.Types.ObjectId(id)) };
    functionIds = (await Func.find(filter, ['_id'])).map((func) => func._id.toString());
    if (functionIds.length === 0) {
      return Promise.resolve();
    }
  }
  await eventHub.emit(eventKind.EVENT_FUNCTION_REMOVE, { functionIds, versionId });
  await Func.deleteMany(filter);
  return eventHub.emit(eventKind.EVENT_FUNCTION_REMOVED, { functionIds, versionId });
});

eventHub.on(eventKind.EVENT_FUNCTION_REMOVE, async ({ functionIds, versionId }) => {
  function removeRelatedData(filter) {
    return Promise.all([
      FunctionVariable.deleteMany(filter),
      FunctionCFG.deleteMany(filter),
      FunctionOriginalVariable.deleteMany(filter),
      FuncBorderVariable.deleteMany(filter),
      BlackBoxCovered.deleteMany(filter),
      ManualCovered.deleteMany(filter),
      BreakPoint.deleteMany(filter),
    ]);
  }
  const filter = { versionId: mongoose.Types.ObjectId(versionId) };
  if (!functionIds) {
    return removeRelatedData(filter);
  }
  return utility.arrayChunkOperation(utility.arrayChunk(functionIds, 500), 1, (funcIds) => {
    filter.functionId = { $in: funcIds.map((id) => mongoose.Types.ObjectId(id)) };
    return removeRelatedData(filter);
  });
});

eventHub.on(eventKind.EVENT_FUNCTION_REMOVED, ({ versionId }) => replaceFanInAndFanOut(versionId));

eventHub.on(eventKind.EVENT_FUNCTION_RESET, async ({ functionIds }) => {
  utility.arrayChunkOperation(functionIds, 5, async (funcId) => {
    const filter = { functionId: funcId };
    const [originalVariable, func] = await Promise.all([
      FunctionOriginalVariable.findOne(filter, { _id: 0, __v: 0 }).lean(),
      Func.findById(funcId),
      removeResultOfExecution(funcId),
      FunctionVariable.deleteOne(filter),
      FuncBorderVariable.deleteOne(filter),
      BlackBoxCovered.deleteOne(filter),
      ManualCovered.deleteOne(filter),
    ]);
    await FunctionVariable.create(originalVariable);
    const data = {
      functionId: funcId,
      fileId: func.fileId,
      versionId: func.versionId,
      creatorId: func.creatorId,
    };
    // 重置测试用例和编组number;
    await tests.reshuffleTestNumber(funcId);
    await testCaseGroups.reshuffleGroupNumber(funcId);
    publisher.publishFunctionMessage('reset', data);
  });
});

/**
 * Export model definition object.
 */
module.exports = {
  fixedAddrBasesAssignmentKind,
  cfgNodeKind,
  resultKind,
  variableKind,
  variableScopeKind,
  integratedFuncKey,
  getBranches,
  getCFG,
  getCFGJson,
  getCoverage,
  getCoveredConditions,
  getInvokeCoverage,
  getIntegrationCoveredDetail,
  getDecisions,
  getDynamicStatistics,
  getEdgeKey,
  getMallocVariableJson,
  getMcdcCases,
  generateHeadComment,
  getStubPointerTargetJson,
  getFixedAddressJson,
  getStubJson,
  getFunctionVariablesOfFile,
  getFunctionVariableJson,
  getIntegrationFixedAddress,
  getIntegrationFixedAddressExprs,
  getFunctionByMangledId,
  getVersionGlobalVariables,
  getIntegratedFunctions,
  getFuncBorderVariable,
  getFunctionCoverages,
  getFunctionByFileLine,

  getFunction,
  formatFuncInvoke,
  getFuncInvokeFiles,
  getFileByMangledIds,
  getFunctionByName,
  getFunctionByKey,
  getFunctions,
  getTreeFuncs,
  getFunctionByVersionId,
  getFunctionKey,
  filter,
  getVariablesOfFile,
  getVariablesOfFunctions,
  getFunctionVariables,
  getFunctionOriginalVariables,
  getFunctionCFG,
  getTestcasesCoverage,
  getMallocVariable,
  getStub,
  getTestCountByFunctions,
  getInvokeCoveredLocations,
  getInvokeFunctionsVariable,
  getRawFunction,
  getSelfDefinedGlobals,

  getStubPointerTarget,
  getCount,
  getCountByExtname,
  getArrayTypeName,
  getFixedAddress,
  get502DesignFlowData,

  addMallocVariable,
  addMallocVariables,
  addStubForConstructor,
  addStubPointerTarget,
  addStubPointerTargets,
  addOutputStaticsVariable,
  addOutputGlobalVariable,
  addOutputMallocVariable,
  addOutputFixedAddress,
  addOutputMallocVariables,
  addOutputObjectVariable,
  addOutputIntegratedFunc,
  addFixedAddressBases,
  addFixedAddressExprs,
  addStub,
  addStubParamsOutput,

  clearFunctionCovered,
  removeResultOfExecution,
  removeResultOfExecutionInVersion,

  removeCfgCoverageWithTestId,
  removeOutputGlobalVariable,
  removeOutputMallocVariable,
  removeOutputObjectVariable,
  removeFixedAddress,
  removeFixedAddressExpr,
  removeOutputFixedAddress,
  removeOutputIntegratedFunc,
  removeOutputStaticsVariable,
  removeStubParamsOutput,

  removeMallocVariable,
  removeStub,
  removeStubPointerTarget,

  replaceFunction,
  replaceFunctionVariables,
  replaceFunctionCFG,
  updateUnread,
  updateCoveredBlocks,
  updateCoveredBranches,
  updateCoveredConditions,
  updateCoveredInvokes,
  updateInvokeCoverageTotal,
  updateFunctionCoverage,
  updateFixedAddress,
  updateFunctionInvokeCoverage,
  updateStubTimes,
  updateFunctionVariable,
  updateFunction,
  updateMallocVariable,
  updateMcdcCases,
  updateStub,
  updateStubPointerTarget,
  updateFunctions,
  updateAllRelationStubStatusByFunctionId,
  updateIntegratedFunctions,
  updateSelfDefinedGlobals,
  updateFunctionComment,
  updateFuncBorderVariable,

  isCFunction,
  isConstructor,
  isCompatible,
  getOverview,
  formatFixedAddressName,
  getUnusedFixedAddresses,
  getExternalGLobalOptionsList,
  invokeGraph,
  getInvokeChildren,
  replaceExternalGlobals,

  integrationTestUpdateFunctionVariableByVariable,
  updateFunctionVariableByVariable,
  integrationUpdateTestCaseStubs,
  integrationSolution,
  integrationSolutionUpdateFile,
  relationFuncsDeduplicate,
  getRelationFuncsVariables,
  getRelationFuncsVariablesV2,
  getUsedTypes,
  mergeFunctionVariables,
  functionVariableSpliceFileId,
  diffVariables,
  getFunctionStubsByVersionId,

  saveIntegrationFixedAddressExprs,
  IntegrationAddInvokeFuncFixedAddressBases,

  handleSpecializationFile,
  handleDuplicateSign,
  handleIntegrationStaticsOutput,
  handleIntegratedGlobalsAnalysis,

  deleteFunctionDuplicateTestcases,
  generateOverviewXlsx,

  onFunctionCoverageSelected,
  getAllFanInAndFanOut,
  replaceFanInAndFanOut,

  // 死循环相关
  setInfiniteLoopTimes,
  getInfiniteLoops,

  callExprTimes,
  calConditionMcdcCovered,
  completeCoverageInfo,
  completeDecisionCases,
  mergeCfgCovered,
  mergeDecisionCasesTestIds,

  manuallyCover,
  manuallyUnCover,
};
exports = Object.assign(exports, module.exports);
